﻿function TimeKeyPress()
{
    var field = event.srcElement;
    var parts = field.value.split(':');
    var hour = (parts.length > 2) ? ParseInt(parts[0]) : 0;
    var min = ParseInt(parts[parts.length - 2]);
    var sec = ParseInt(parts[parts.length - 1]);
    var changed = false;
    if (event.keyCode == 8) // backspace
    {
        sec = Math.floor(sec / 10 + (min % 10) * 10);
        min = Math.floor(min / 10 + hour * 10);
        hour = 0;
        changed = true;
    }
    if (event.keyCode >= 48 && event.keyCode <= 57)
    {
        hour = Math.floor(min / 10);
        min = Math.floor((min % 10) * 10 + sec / 10);
        sec = Math.floor((sec % 10) * 10 + (event.keyCode - 48));
        changed = true;
    }
    if (changed)
    {
        field.value = 
            (parts.length > 2 ? (hour.toString() + ":") : "") +
            (100 + min).toString().substr(1,2) + ":" +
            (100 + sec).toString().substr(1,2);
        return false;
    }
}

function setGlobalsReadOnly(readonly)
{
    $('PoolLength').readonly = readonly ? "readonly" : ""; 
}
function getPoolLength()
{
    return parseFloat(PoolLength.value);
}
function setPoolLength(value)
{
    PoolLength.value = value;
}
function applyPoolLength()
{
    if (PoolLength.value != PoolLength.original)
    {
        if (!validateInput(PoolLength, "Pool length is out of range"))
            return;
        SetDirty();
        var i;
        for (i = 0; i < exerSegs(); i++)
            exerRow(i).recalcDist();
        if (AllSegmentsIdentical())
        {
            for (i = 0; i < exerSegs(); i++)
                exerRow(i).recalcTurnTime();
            recalcGlobalTurnTime();
        }
        recalcGlobals();
        ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    }
}
function getPlace()
{
    return $('PaceMode').value;
}
function getStrideStrokeLength()
{
    return parseFloat(StrideStrokeLength.value);
}
function setStrideStrokeLength(value)
{
    StrideStrokeLength.value = value;
}
function getStridesStrokesBeep()
{
    return parseFloat(StridesStrokesBeep.value);
}
function setStridesStrokesBeep(value)
{
    StridesStrokesBeep.value = value;
}
function getMarkerDistance()
{
    return parseFloat(MarkerDistance.value);
}
function setMarkerDistance(value)
{
    MarkerDistance.value = value;
}
function applyMarkerDistance()
{
    if (MarkerDistance.value != MarkerDistance.original)
    {
        if (!validateInput(MarkerDistance, "Marker Distance is out of range"))
            return;
        SetDirty();
//        var i;
//        for (i = 0; i < exerSegs(); i++)
//            exerRow(i).recalcDist();
//        if (AllSegmentsIdentical())
//        {
//            for (i = 0; i < exerSegs(); i++)
//                exerRow(i).recalcTurnTime();
//            recalcGlobalTurnTime();
//        }
//        recalcGlobals();
        ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    }
}
function getPaceUnits()
{
    return DistanceUnits.innerText;
}
function getGlobalLaps()
{
    return ParseInt(Laps.value);
}
function setGlobalLaps(value)
{
    Laps.value = value;
}
function recalcGlobalLaps()
{
    var original = getGlobalLaps();
    var total = 0;
    for (var i = 0; i < exerSegs(); i++)
        total += exerRow(i).getLaps();
    setGlobalLaps(total);
    if (IsSwim)
        Distance.innerText = Math.round(getGlobalLaps() * getPoolLength());
    return (getGlobalLaps() != original);
}
function applyGlobalLaps()
{
    if (Laps.original != Laps.value)
    {
        if (!validateInput(Laps, "Number of laps is out of range"))
            return;
        var original = ParseInt(Laps.original);
        var target = getGlobalLaps();
        // if the target value is not valid, try several times in the same change direction (in terms of value)
        var attempts = 0;
        while ((target % exerSegs()) != 0 && // (original % target) != 0 && (target % original) != 0 && 
            attempts++ < 10 && (target > original || target > 1))
        {
            target += (target > original ? 1 : -1);
        }
        // check and notify of invalid target values
        if ((target % exerSegs()) != 0) // (original % target) != 0 && (target % original) != 0)
        {
            Laps.focus();
            UserAlert("Cannot apply new laps value");
        }
        else // apply valid value by distributing it over existing segments
        {
            SetDirty();
            var segs = exerSegs();
            for (var i = 0; i < segs; i++)
            {
                var seg = exerRow(i);
                seg.setLaps(target / segs); // seg.getLaps() * target / original);
                seg.recalcDist();
            }
        }
        recalcGlobals(true); // graph redraw mandatory
        ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    }
}
function getGlobalDistance()
{
    return IsSwim ? getGlobalLaps() * getPoolLength() : parseInt(RunDistance.value);
}
function setGlobalDistance(value)
{
    if (IsSwim)
    {
        UserAlert("FATAL ERROR: Run logic used in swim!");
        return;
    }
    RunDistance.value = value;
}
function recalcGlobalDistance()
{
    if (IsSwim)
    {
        UserAlert("FATAL ERROR: Run logic used in swim!");
        return;
    }
    var original = getGlobalDistance();
    var total = 0;
    for (var i = 0; i < exerSegs(); i++)
        total += exerRow(i).getDist();
    setGlobalDistance(total);
    return (getGlobalDistance() != original);
}
function applyGlobalDistance()
{
    if (IsSwim)
    {
        UserAlert("FATAL ERROR: Run logic used in swim!");
        return;
    }
    if (RunDistance.original != RunDistance.value)
    {
        if (!validateInput(RunDistance, "Total run distance is out of range"))
            return;
        var original = ParseInt(RunDistance.original);
        var target = getGlobalDistance();
        // check and notify of invalid target values
        if ((target % getMarkerDistance()) != 0)
        {
            RunDistance.focus();
            UserAlert("Cannot apply new total run distance value");
        }
        else // apply valid value by distributing it over existing segments
        {
            SetDirty();
            var segs = exerSegs();
            var markerdistseg = Math.floor((target / segs + getMarkerDistance() - 1) / getMarkerDistance()) * getMarkerDistance();
            for (var i = 0; i < segs; i++)
            {
                var seg = exerRow(i);
                seg.setDist(markerdistseg); // target / segs);
                seg.recalcTime();
            }
            exerRow(exerSegs() - 1).setDist(target - (exerSegs() - 1) * markerdistseg);
        }
        recalcGlobals(true); // graph redraw mandatory
        ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    }
}
function getGlobalSegments()
{
    return ParseInt(Segments.value);
}
function setGlobalSegments(value)
{
    Segments.value = value;
}
function recalcGlobalSegments()
{
    var original = getGlobalSegments();
    setGlobalSegments(exerSegs());
    return (getGlobalSegments() != original);
}
function applySegments()
{
    var i;
    if (Segments.original != Segments.value)
    {
        if (!validateInput(Segments, "Number of segments is out of range"))
            return;
        var original = ParseInt(Segments.original);
        var target = getGlobalSegments();
        if (target < 1 || target > 30)
        {
            Segments.focus();
            UserAlert("Number of segments is out of range");
        }
        else 
        {
            if (IsSwim)
            {
                // try in the direction of change until we have a target segments number
                // into which the existing total laps can be distributed evenly
                var laps = getGlobalLaps();
                var attempt = 0;
                while (target > 1 && target <= 30 && target <= laps && attempt++ < 20 && (laps % target) != 0)
                    target += (target > original) ? 1 : -1;
                if (target < 1 || target > 30 || target > laps || attempt >= 20)
                {
                    Segments.focus();
                    UserAlert("Number of segments is out of range");
                    setGlobalSegments(original);
                    return;
                }
            }
            
            SetDirty();

            // set target value intermediatelly although recalcGlobals below does the same,
            // so users won't catch the initial invalid value
            setGlobalSegments(target);
            // apply new segments number by deleting or adding segments
            while (exerSegs() > target)
                deleteRow(1, false);
            while (exerSegs() < target)
                duplicateRow(1, false);
                
            if (IsSwim)
            {
                // redistributed original total laps (before row adjustment above)
                // evenly into resulting segments
                for (i = 0; i < exerSegs(); i++)
                {
                    exerRow(i).setLaps(laps / target);
                    exerRow(i).recalcDist();
                }
            }
            if (IsRun)
            {
                // redistribute original total distance evenly into resulting segments while keeping
                var markerdistseg = Math.floor((getGlobalDistance() / exerSegs()
                    //+ getMarkerDistance() - 1
                    ) / getMarkerDistance()) * getMarkerDistance();
                //window.status = markerdistseg;
                for (i = 0; i < exerSegs() - 1; i++)
                {
                    exerRow(i).setDist(markerdistseg);
                    exerRow(i).recalcTime();
                }
                exerRow(exerSegs() - 1).setDist(getGlobalDistance() - (exerSegs() - 1) * markerdistseg);
            }
                        
            // scroll the top row into view
            //exerRow(0).scrollIntoView();
            exergrid.parentElement.scrollTop = 0;
        }
        recalcGlobals(true); // graph redraw mandatory
        ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    }
}
function getGlobalPace()
{
    return getGlobalDistance() / getGlobalSwimTime();
}
function getMaxPace()
{
    var maxpace = 0;
    for (var i = 0; i < exerSegs(); i++)
        maxpace = Math.max(maxpace, exerRow(i).getPace());
    return maxpace;
}
function getGlobalSwimTime()
{
    return parseHMMSS(SwimTime.value);
}
var nonEqualizerGlobalTime = 0;
function setGlobalSwimTime(value)
{
    SwimTime.value = toHMMSS(value);
    nonEqualizerGlobalTime = getGlobalSwimTime();
}
function recalcGlobalSwimTime()
{
    var original = getGlobalSwimTime();
    var total = 0;
    for (var i = 0; i < exerSegs(); i++)
        total += exerRow(i).getTime();
    setGlobalSwimTime(total);
    return (getGlobalSwimTime() != original);
}
function applySwimTime()
{
    if (SwimTime.original != SwimTime.value)
    {
        var original = parseHMMSS(SwimTime.original);
        var target = getGlobalSwimTime();
        adjustExerciseTime(original, target);
        nonEqualizerGlobalTime = getGlobalSwimTime();
    }
}
function adjustExerciseTime(original, target)
{
    var i, seg;
    // check if the application of new target swim time won't cause any segment to be out of valid values:
    for (i = 0; i < exerSegs(); i++)
    {
        seg = exerRow(i);
        var error = null;
//        // to overflow into 1 hour or more
//        if (seg.getTime() * target / original >= 3600)
//            error = "A segment time will be out of range";
        // to produce pace of less than 1 km/h or mph, or more than MaxPaceUnits (8km/h or 4mph)
        var pace = seg.getPace() / target * original * 3.6;
        if (pace < 1 || pace > MaxPaceUnits)
            error = "A segment time will be out of range";
        // if segment out of valid values, inform user and revent to original value
        if (error != null)
        {
            UserAlert(error);
            SwimTime.value = SwimTime.original;
            SwimTime.focus();
            return;
        }
    }
    // apply new target swim time relative to previous value in each segment
    SetDirty();
        var total1 = 0;
        for (i = 0; i < exerSegs(); i++)
            total1 += exerRow(i).getTime();
    for (i = 0; i < exerSegs(); i++)
    {
        seg = exerRow(i);
        seg.setTime(seg.getTime() * target / original);
        seg.recalcPace();
    }
    var total2 = 0;
    for (i = 0; i < exerSegs(); i++)
        total2 += exerRow(i).getTime();
    if (IsSwim && AllSegmentsIdentical())
    {
        for (i = 0; i < exerSegs(); i++)
            exerRow(i).recalcTurnTime();
        recalcGlobalTurnTime();
    }
    setGlobalSwimTime(target);
    var before = getGlobalSwimTime();
    recalcGlobals(true);
    //window.status = "original:" + original + " target:" + target + " before:" + before + " after:" + getGlobalSwimTime();
}
function getGlobalRestTime()
{
    return parseHMMSS(RestTime.value);
}
function getMaxRestTime()
{
    var maxrest = 0;
    for (var i = 0; i < exerSegs(); i++)
        maxrest = Math.max(maxrest, exerRow(i).getRest());
    return maxrest;
}
function setGlobalRestTime(value)
{
    RestTime.value = toHMMSS(value);
}
function recalcGlobalRestTime()
{
    var original = getGlobalRestTime();
    var total = 0;
    for (var i = 0; i < exerSegs(); i++)
        total += exerRow(i).getRest();
    setGlobalRestTime(total);
    return (getGlobalRestTime() != original);
}
function applyRestTime()
{
    var i, seg, res;
    if (RestTime.original != RestTime.value)
    {
        var original = parseHMMSS(RestTime.original);
        var target = getGlobalRestTime();
        var failed = null;
        // readjust all existing rests according to new global rest
        if (original > 0) 
        {
            // check if the application of new target rest time won't cause any segment
            // to overflow into 10 minutes rest or more
            for (i = 0; i < exerSegs(); i++)
            {
                seg = exerRow(i);
                res = seg.getRest() * target / original;
                if (res > 0 && (res < 10 || res >= 600))
                    failed = "A segment rest will be out of range";
            }
            if (failed == null)
                // scale existing rest values
                for (i = 0; i < exerSegs(); i++)
                {
                    seg = exerRow(i);
                    seg.setRest(seg.getRest() * target / original);
                }
        }
        // try to distribute rests from new global rest
        else 
        {
            // check if the application of new target rest time won't cause all segment
            // to overflow into 10 minutes rest or more
            res = target / (exerSegs() - 1);
            if (res > 0 && (res < 10 || res >= 600))
                failed = "A segment rest will be out of range";
            else
                // distribute new global rest to all rows but the last
                for (i = 0; i < exerSegs() - 1; i++)
                {
                    seg = exerRow(i);
                    seg.setRest(target / (exerSegs() - 1));
                }
        }
        if (failed == null)
        {
            SetDirty();
            recalcGlobals(true);
        }
        else
        {
            UserAlert(failed);
            RestTime.value = RestTime.original;
            RestTime.focus();
        }
    }
}
function getGlobalTurnTime()
{
    return parseFloat(TurnTime.value);
}
function setGlobalTurnTime(value)
{
    TurnTime.value = value.toFixed(1);
}
function recalcGlobalTurnTime()
{
    var original = TurnTime.value;
    var total = 0;
    for (var i = 0; i < exerSegs(); i++)
        total += (exerRow(i).getLaps() * exerRow(i).getTurnTime());
    setGlobalTurnTime(Math.min(total / getGlobalLaps(), parseFloat(TurnTime.maxvalue))); // 5)); // max turn time (turn-adjust)
    return (TurnTime.value != original);
}
function applyTurnTime()
{
    var i, seg;
    if (TurnTime.original != TurnTime.value)
    {
        var original = parseFloat(TurnTime.original);
        var target = getGlobalTurnTime();
        var failed = null;
        if (("minvalue" in TurnTime) && target < parseFloat(TurnTime.minvalue))
            failed = "A turn time is out of range";
        else if (("maxvalue" in TurnTime) && target > parseFloat(TurnTime.maxvalue))
            failed = "A turn time is out of range";
        else
        {
            // first check that no segment results in invalid turn-time
            var template = exerRow(0);
            var minvalue = ("minvalue" in template.TurnTime) ? parseFloat(template.TurnTime.minvalue) : null;
            var maxvalue = ("maxvalue" in template.TurnTime) ? parseFloat(template.TurnTime.maxvalue) : null;
            for (i = 0; i < exerSegs() && failed == null; i++)
            {
                seg = exerRow(i);
                var res = seg.getTurnTime() * target / original;
                if (minvalue != null && res < minvalue)
                    failed = "A segment Turn will be out of range";
                if (maxvalue != null && res > maxvalue)
                    failed = "A segment Turn will be out of range";
            }
            if (failed == null)
            {
                SetDirty();
                for (i = 0; i < exerSegs(); i++)
                {
                    seg = exerRow(i);
                    seg.setTurnTime(seg.getTurnTime() * target / original);
                }
                recalcGlobalTurnTime();
                recalcGlobals();
            }
        }
        if (failed != null)
        {
            TurnTime.focus();
            UserAlert(failed);
            setGlobalTurnTime(original);
        }
    }
}
function AllSegmentsIdentical()
{
    var i;
    if (IsSwim)
        for (i = 1; i < exerSegs(); i++)
            if (exerRow(i).getLaps() != exerRow(0).getLaps() ||
                exerRow(i).getTime() != exerRow(0).getTime() ||
                (i < exerSegs() - 1 && exerRow(i).getRest() != exerRow(0).getRest()) ||
                exerRow(i).getTurnTime() != exerRow(0).getTurnTime())
                    return false;
    if (IsRun)
    {
        // check that all segments are identical (other than the last)
        for (i = 1; i < exerSegs() - 1; i++)
            if (exerRow(i).getDist() != exerRow(0).getDist() || 
                exerRow(i).getPace() != exerRow(0).getPace() || 
                exerRow(i).getRest() != exerRow(0).getRest())
                    return false;
        // check if last segment is about the same as its predecessor
        if (exerRow(0).getDist() - exerRow(exerSegs() - 1).getDist() > exerSegs() * getMarkerDistance())
            // || Math.abs(exerRow(0).getPace() - exerRow(exerSegs() - 1).getPace()) < 0.1)
            return false;
    }
    return true;
}
function EnableGlobalField(fld, disabled)
{
    fld.readOnly = disabled;
    fld.className = disabled ? "readonlyinput" : "";
    var updown = $(fld.id + "UpDown");
    if (updown != null)
        updown.style.display = disabled ? "none" : "";
}
function recalcGlobals(drawgraph, recalcturntime)
{
    var lapsc = IsSwim && recalcGlobalLaps();
    var segsc = recalcGlobalSegments();
    var distc = IsRun && recalcGlobalDistance();
    var swimc = recalcGlobalSwimTime();
    var restc = recalcGlobalRestTime();
    var same = AllSegmentsIdentical();
    if (recalcturntime && IsSwim)
        recalcGlobalTurnTime();
    if (IsSwim)
        EnableGlobalField(Laps, !same);
    EnableGlobalField(Segments, !same);
    //EnableGlobalField(TurnTime, !same);
    if (drawgraph || lapsc || segsc || distc || swimc || restc)
        RedrawGraph();
}
