﻿var ExerName;
var PaceUnits = "km/h";
var MaxPaceUnits = 40; // 40km/h for run; swim is 8km/h or 6mph
var FullPaceUnitsFactor = 3.6; // 3.6 for m/s to km/h; 2.237 for yard/s to mph
var Dirty; // has anything changed in the exercise?

function SetExerName(name)
{
    ExerName = name;
    ExerciseCaption.innerHTML = "Exercise: <b>" + name.replace(/ /g, "&nbsp;") + "</b>";
}

function SetDirty()
{
    Dirty = true;
}

function ClearDirty()
{
    Dirty = false;
}

function exerSegs() 
{ 
    for (var i = 1; i < exergrid.rows.length; i++)
        if ("isBlank" in exergrid.rows[i])
            return i - 1;
    return exergrid.rows.length - 1; 
}
function curRow() { return FindParent(event.srcElement, "TR"); }
function exerRow(rowIndex) { return exergrid.rows[rowIndex + 1]; }
function exerRowIndex(tr) { return tr.rowIndex - 1; }

function activeRow()
{
    return exerRow(event.srcElement.parentElement.parentElement.rowIndex - 1);
}

function deleteRow(rowIndex, recalc)
{
    if (rowIndex == null)
        rowIndex = event.srcElement.parentElement.parentElement.rowIndex;
    exergrid.deleteRow(rowIndex);
    padRows();
    exerRow(exerSegs() - 1).setRestVisible(false); // make sure the last rest is invisible as we may have just delete the old last row
    if (recalc != false)
    {
        recalcGlobals(false, true);
        //-see above: recalcGlobalTurnTime();
    }
}

function nextRow(elem) 
{ 
    var tr = FindParent(elem, "TR");
    var index = exerRowIndex(tr);
    return (index < exerSegs() - 1) ? exerRow(index + 1) : exerRow(0);
}

function addRow(after)
{
    var templ = exergrid.rows[0];
    var tr = exergrid.insertRow(after+1);
    //for (st in templ.style)
    //    tr.style[st] = templ.style[st];
    tr.style.backgroundColor = templ.style.backgroundColor; //PATCH
    for (var i = 0; i < templ.cells.length; i++)
    {
        var td = tr.insertCell();
        td.innerHTML = templ.cells[i].innerHTML;
        td.style.display = templ.cells[i].style.display;
        td.style.width = templ.cells[i].style.width;
    }
    tr.style.display = "block";

    var cell = 1;
    if (IsSwim)
    {
        tr.Laps = tr.childNodes[cell++].firstChild;
        tr.Dist = tr.childNodes[cell++].firstChild;
        tr.Time = tr.childNodes[cell++].firstChild;
        tr.Pace = tr.childNodes[cell++].firstChild;
        tr.Rest = tr.childNodes[cell++].firstChild;
        tr.TurnTime = tr.childNodes[cell++].firstChild;
    }
    if (IsRun)
    {
        tr.Dist = tr.childNodes[cell++].firstChild;
        tr.Time = tr.childNodes[cell++].firstChild;
        tr.Pace = tr.childNodes[cell++].firstChild;
        tr.Rest = tr.childNodes[cell++].firstChild;
    }
        
    tr.getDist = function() { return parseFloat(this.Dist.innerText); }
    tr.setDist = function(value) { this.Dist.innerText = Math.round(value); this.recalcTime(); }
    tr.getTime = function() { return parseHMMSS(this.Time.innerText); }
    tr.setTime = function(value) { this.Time.innerText = value >= 3600 ? toHMMSS(value) : toMMSS(value); }
    tr.getPace = function() { return this.Pace.value; } // parseFloat(this.Pace.innerText) / FullPaceUnitsFactor; }
    tr.setPace = function(value) { this.Pace.value = value; this.Pace.innerText = (value * FullPaceUnitsFactor).toFixed(1); this.recalcTime(); }
    tr.getRest = function() { return parseMMSS(this.Rest.innerText); }
    tr.setRest = function(value) { this.Rest.innerText = toMSS(value); }
    tr.recalcDist = function() { this.setDist(this.getLaps() * getPoolLength()); this.recalcTime(); }
    tr.recalcPace = function() { this.setPace(this.getDist() / this.getTime()); }
    tr.recalcTime = function() { this.setTime((tr.getPace() == 0) ? 0 : (tr.getDist() / tr.getPace())); }
    if (IsSwim)
    {
        tr.getLaps = function() { return parseFloat(this.Laps.innerText); }
        tr.setLaps = function(value) { this.Laps.innerText = value; this.recalcDist(); }
        tr.getTurnTime = function() { return parseFloat(this.TurnTime.innerText); }
        tr.setTurnTime = function(value) { this.TurnTime.innerText = value.toFixed(1); }
        tr.recalcTurnTime = function () { this.setTurnTime(Math.min(this.getTime() * 2 / this.getDist(), 5)); } // limit to max turn time
    }
    tr.setRestVisible = function(show) { this.Rest.style.display = (show ? "" : "none"); }
    tr.isRestVisible = function() { return this.Rest.style.display != "none"; }
//        tr.Laps.focus();
//        selectNode(tr.Laps);
    return tr;
}
// append row at the end with specified data
function appendRow(args)
{
    var tr = addRow(exerSegs());
    if ("Laps" in args)
    {
        tr.setLaps(args.Laps);
        //tr.recalcDist();
    }
    else if ("Dist" in args)
        tr.setDist(args.Dist);
    tr.setTime(args.Time);
    tr.recalcPace();
    //if ("Rest" in args)
        tr.setRest(("Rest" in args) ? args.Rest : 0);
    if (IsSwim)
        tr.setTurnTime(("Turn" in args) ? args.Turn : getGlobalTurnTime());
    if (exerSegs() > 1)
        tr.previousSibling.setRestVisible(true);
    tr.setRestVisible(false);
}
//function appendRow(laps, time, rest, turn)
//{
//    var tr = addRow(exerSegs());
//    tr.setLaps(laps);
//    tr.recalcDist();
//    tr.setTime(time);
//    tr.recalcPace();
//    tr.setRest(rest);
//    tr.setTurnTime((turn == null) ? getGlobalTurnTime() : turn);
//    if (exerSegs() > 1)
//        tr.previousSibling.setRestVisible(true);
//    tr.setRestVisible(false);
//    recalcGlobals();
//}
//// duplicate an existing row after specified position
function duplicateRow(after, recalc)
{
    var tr = addRow(after);        
//        tr.setTime(0);
//        tr.setRest(0);
    var prev = tr.previousSibling;
    if (IsSwim)
        tr.setLaps(prev.getLaps());
    tr.setDist(prev.getDist());
    tr.setTime(prev.getTime());
    tr.setPace(prev.getPace());
    tr.setRest(prev.getRest());
    if (IsSwim)
        tr.setTurnTime(prev.getTurnTime());
    tr.previousSibling.setRestVisible(true); // make previous row's rest visible (if it was last)
    tr.setRestVisible(tr.rowIndex < exerSegs()); // make the new duplicated row's rest invisible if it is last (!)
    padRows();
    if (recalc != false)
    {
        recalcGlobals();
        if ((IsSwim && getGlobalLaps() >= 200) || (IsRun && getGlobalDistance() >= 50000))
        {
            if (tr.rowIndex == exerSegs())
                tr.previousSibling.setRestVisible(false); // make previous row's rest invisible (if it was last)
            tr.parentElement.deleteRow(tr.rowIndex); // delete new row just added
            padRows();
            recalcGlobals();
            UserAlert(IsSwim ? "Maximum number of laps is 199" : "Maximum distance is 50,000 meters");
        }
    }
    //PATCH: we used this to ensure new rows that may be below "scroll line" of the
    //       exercise table will be scrolled into view; however, on FaceBook this seems
    //       to produce unwieldy scroll in the entire FaceBook UI
    //tr.scrollIntoView();
}
// make sure we have a total of specified visible rows, even if we need to pad it with empty rows;
// if we have empty rows where the table is more than 8 rows total, trim it
function padRows()
{
    var j;
    while (exergrid.rows.length > VISIBLE_ROWS+1 && ("isBlank" in exergrid.rows[exergrid.rows.length - 1]))
        exergrid.deleteRow(exergrid.rows.length - 1);
    var templ = exergrid.rows[0];
    var padcells = 0;
    for (j = 1; j < exergrid.rows(0).cells.length; j++)
        if (exergrid.rows(0).cells[j].style.display != "none")
            padcells++;
    while (exergrid.rows.length < VISIBLE_ROWS+1)
    {
        var tr = exergrid.insertRow();
        tr.isBlank = true;
        tr.style.backgroundColor = templ.style.backgroundColor; //PATCH
        //tr.style.height = "18px"; // to match the add(+) and remove(-) icons
        tr.insertCell().innerHTML = "<img src='images/empty.png'>"; // to match the add(+) and remove(-) icons
        for (j = 0; j < padcells; j++)
            tr.insertCell().innerHTML = "&nbsp;";
    }
}
function lapsChanged()
{
    var tr = activeRow();
    if (tr.Laps.original != tr.Laps.value)
    {
        var original = parseFloat(tr.Laps.original);
        var target = tr.getLaps();
        if (("minvalue" in tr.Laps) && target < parseFloat(tr.Laps.minvalue))
        {
            tr.Laps.focus();
            UserAlert("Number of laps is out of range");
            tr.setLaps(original);
        }
        else if (("maxvalue" in tr.Laps) && target > parseFloat(tr.Laps.maxvalue))
        {
            tr.Laps.focus();
            UserAlert("Number of laps is out of range");
            tr.setLaps(original);
        }
        else
        {
            SetDirty();
            activeRow().recalcDist();
            recalcGlobals();
            if (getGlobalLaps() > parseFloat(Laps.maxvalue))
            {
                tr.Laps.focus();
                UserAlert("Number of laps is out of range");
                tr.setLaps(original);
                activeRow().recalcDist();
                recalcGlobals();
            }
            else
            {
                recalcGlobalTurnTime();
                ForceRefreshGrid(); // force-refreshing first row is enough for entire table
            }
        }
    }
}
function distChanged()
{
    SetDirty();
    activeRow().recalcTime();
    recalcGlobals();
    if ((IsSwim && getGlobalLaps() >= 200) || (IsRun && getGlobalDistance() > 50000))
    {
        UserAlert(IsSwim ? "Maximum number of laps is 199" : "Total run distance is out of range");
        return false;
    }
    ForceRefreshGrid(); // force-refreshing first row is enough for entire table
    return true;
}
function timeChanged()
{
    var row = activeRow();
    var original = parseMMSS(row.Time.original);
    var failed = null;
    activeRow().recalcPace();
    if (row.getPace()*FullPaceUnitsFactor < 1)
        failed = "This segment time will be out of range"; // "The resulting pace is less than 1 " + PaceUnits;
    if (row.getPace()*FullPaceUnitsFactor > MaxPaceUnits)
        failed = "This segment time will be out of range"; // "The resulting pace is greater than " + MaxPaceUnits + " " + PaceUnits;
    if (failed == null)
    {
        SetDirty();
        row.setTime(row.getTime()); // format content
        recalcGlobals();
    }
    else
    {
        row.Time.focus();
        UserAlert(failed);
        row.setTime(original);
        row.recalcPace();
    }
}
function paceChanged()
{
    var row = activeRow();
    var original = parseFloat(row.Pace.original) / FullPaceUnitsFactor;
    var failed = null;
    var newpace = parseFloat(row.Pace.innerText) / FullPaceUnitsFactor;
    if (newpace*FullPaceUnitsFactor < 1)
        failed = "This segment time will be out of range";
    if (newpace*FullPaceUnitsFactor > MaxPaceUnits)
        failed = "This segment time will be out of range";
    if (failed == null)
    {
        row.setPace(newpace);
        SetDirty();
        row.recalcTime();
        recalcGlobals();
        if ((IsSwim && getGlobalLaps() >= 200) || (IsRun && getGlobalDistance() >= 50000))
        {
            //UserAlert(IsSwim ? "Maximum number of laps is 199" : "Maximum distance is 50,000 meters");
            return false;
        }
    }
    else
    {
        row.Pace.focus();
        UserAlert(failed);
        row.setPace(original);
        row.recalcPace();
    }
    return true;
}
function restChanged()
{
    var row = activeRow();
    var original = parseMMSS(row.Rest.original);
    var failed = null;
    if (row.getRest() > 0 && row.getRest() < parseFloat(row.Rest.minvalue))
        failed = "The minimum rest value is " + parseFloat(row.Rest.minvalue) + " seconds";
    if (row.getRest() > 0 && row.getRest() > parseFloat(row.Rest.maxvalue))
        failed = "The maximum rest value is " + parseFloat(row.Rest.maxvalue) + " seconds";
    if (failed == null)
    {
        SetDirty();
        row.setRest(row.getRest()); // format content
        //recalcGlobalRestTime();
        recalcGlobals();
    }
    else
    {
        row.Rest.focus();
        UserAlert(failed);
        row.setRest(original);
    }
}
function turnTimeChanged()
{
    var tr = activeRow();
    if (tr.TurnTime.original != tr.TurnTime.value)
    {
        var original = parseFloat(tr.TurnTime.original);
        var target = tr.getTurnTime();
        if (("minvalue" in tr.TurnTime) && target < parseFloat(tr.TurnTime.minvalue))
        {
            tr.TurnTime.focus();
            UserAlert("A turn time is out of range");
            tr.setTurnTime(original);
        }
        else if (("maxvalue" in tr.TurnTime) && target > parseFloat(tr.TurnTime.maxvalue))
        {
            tr.TurnTime.focus();
            UserAlert("A turn time is out of range");
            tr.setTurnTime(original);
        }
        else
        {
            SetDirty();
            tr.setTurnTime(target); // reformat input if needed
            recalcGlobalTurnTime();
            recalcGlobals(); // to enable/disable global laps and segments 
        }
    }
}

function FocusFirstGlobalField()
{
    if (IsSwim)
        PoolLength.focus();
    else if (IsRun)
        $('PaceMode').focus();
//        if (getPlace() == "FixedMarkerPaceMode")
//            MarkerDistance.focus();
//        else
//            StrideStrokeLength.focus();
}

function DisplayPaceModeOption(value)
{
    if (value == 'FixedMarkerPaceMode')
        ShowHide($('FixedMarkerPaceMode'), $('StrideStrokePaceMode'));
    else 
        ShowHide($('StrideStrokePaceMode'),$('FixedMarkerPaceMode'));
}

function ClearExercise()
{
    SetExerName('');
    if (IsSwim)
        SelectPaceUnit(DistanceUnits.innerText);
    //- NOW USED BY BOTH SWIM AND RUN   if (IsRun)
        DisplayPaceModeOption($('PaceMode').value);
    // clear all existing rows
    while (exerSegs() > 0)
        exergrid.deleteRow(1);
    // append a first single default row (unless in guest log-view mode)
    if (!FS.IsGuestLog())
    {
        SetExerName('Default');
        if (IsSwim)
            appendRow({ Laps:1, Time:getPoolLength()*1.2 });
        else if (IsRun)
            appendRow({ Dist:1000, Time:360 });
        // calculate first row's (default) turn-time by calculating a single flag's time (2m)
        if (IsSwim)
        {
            exerRow(0).recalcTurnTime();
            recalcGlobals(false); // must have that to ensure proper globalLaps and later globalTurnTime
            recalcGlobalTurnTime();
        }
        //-there is a call below:recalcGlobals();
    }
    //
    padRows();
    recalcGlobals(true); // and RedrawGraph();
    ClearDirty();
    if (!FS.IsGuestLog())
        FocusFirstGlobalField();
}
function NewExercise()
{
    if (Dirty)
        UserConfirm("Discard changes?", function(okay) { if (okay) ClearExercise(); });
    else
        ClearExercise();
}
function SelectPaceUnit(value)
{
    if (IsSwim)
        SelectRadio(Unit, value);
    DistanceUnits.innerText = value; 
    if (value == "meters")
    {
        PaceUnits = 'km/h'; 
        MaxPaceUnits = 8; 
        FullPaceUnitsFactor = 3.6;
    }
    if (value == "yards")
    {
        PaceUnits = 'mph'; 
        MaxPaceUnits = 8; 
        FullPaceUnitsFactor = 2.237; 
    }
    for (var i = 0; i < exerSegs(); i++)
    {
        var seg = exerRow(i);
        seg.recalcPace();
    }
    RedrawGraph();
}

function ValidateFileName(value)
{
    value = value.replace(/&nbsp;/g, " ");
    if (value.search(/\\|:|\*|\057|%|&/) >= 0 || value.indexOf("'") >= 0 || value.indexOf('.') >= 0 || value.indexOf('+') >= 0 || value.indexOf('?') >= 0)
        return "Cannot include * / \\ : % & . + ? '";
    return "";
}

function ExerciseNameValidate()
{
//    if (UserInputValue.value.search(/\\|:|\*|\057|%|&/) >= 0 || UserInputValue.value.indexOf('.') >= 0 || UserInputValue.value.indexOf('+') >= 0)
//        return "Name cannot include * / \\ : % & . +";
//    return "";
    var v = ValidateFileName(UserInputValue.value);
    if (v != "")
        return v;
    for (var i = 0; i < CurrentFolder.Exercises.length; i++)
        if (CurrentFolder.Exercises[i] != null && 
            CurrentFolder.Exercises[i].Name.toLowerCase() == UserInputValue.value.toLowerCase() &&
            UserInputValue.value.toLowerCase() != ExerName.toLowerCase())
                return "Name already exists";
    return "";
}

function MP3NameValidate()
{
    var v = ValidateFileName(UserInputValue.value);
    if (v != "")
        return v;
    return "";
}

function FolderNameValidate()
{
//    if (UserInputValue.value.search(/\\|:|\*|\057|%|&/) >= 0 || UserInputValue.value.indexOf('.') >= 0 || UserInputValue.value.indexOf('+') >= 0)
//        return "Name cannot include * / \\ : % & . +";
    var v = ValidateFileName(UserInputValue.value);
    if (v != "")
        return v;
    for (var i = 0; i < CurrentFolder.Folders.length; i++)
        if (CurrentFolder.Folders[i] != null && CurrentFolder.Folders[i].toLowerCase() == UserInputValue.value.toLowerCase())
            return "Name already exists";
    return "";
}

function TrySaveExercise()
{
    if (!FS.CanSave()) // tooltip should have instructed the user already; click does nothing
        return;
        
    if (FS.IsGuestLog())
    {
        if (LoginUserID == null) // if we don't have a target user, ask for one through "login"
            OpenPopupDialog($('LoginRegisterPane').firstChild.innerHTML.replace(/id=_/g, "id=")
                .replace('<H3>Log On', '<H3>Save')
                .replace('style="DISPLAY: none" onclick=DialogClose()>Cancel', 'onclick=DialogClose()>Cancel')
                .replace('onclick=Login()>Log On', 'onclick=SendLog()>Send To'),
                { left:100, width: 290, height: 160 });
        else 
            UserAlert("Cannot save log exercise"); // should never happen under current logic!
//            // we already have a "target" valid user
//            UserInput("Exercise name", 
//                function(response) 
//                { 
//                    if (response != null)
//                    {
//                        SetExerName(response);
//                        SaveExercise();
//                    }
//                },
//                ExerName,
//                ExerciseNameValidate);
        return; // get out, don't continue to standard non-guest logic
    }
    else if (FS.IsMyLog())
    {
        OpenLogDialog();
    }

//    else if (FS.IsSpecial())
////    //-else if (FS.IsRoot() || FS.IsReceivedFriends())
////        UserAlert("Please open a folder, such as My Exercises");
//        ; // do nothing, as a tooltip pops up to instruct the user
//        
//-    else if (FS.IsSpecial())
//-    {
//-        OpenRootFolder(true); // and into root folder and refresh exercise list
//-        SetExerName(ExerName); // where we place a named copy of the public exercise opened
//-        SaveExercise(); // and save it in the private root
//-    }
    else 
    {
        UserInput("Exercise name", 
            function(response) 
            { 
                if (response != null)
                {
//                    if (FS.Current == FS.Root)
//                        OpenFolder("My Exercises");
                    SetExerName(response);
                    SaveExercise();
                }
            },
            ExerName,
            ExerciseNameValidate);
    }
}

function SaveExercise()
{
    UserNotify("Saving exercise...");
    ajax("Handler.ashx/" + GetPath(ExerName) + "?act=saveexercise" + 
        (FS.IsGuestLog() ? ("&targetuser=" + LoginUserID) : "") +
        "&exer=" + exerState(),
        null, 
        function(resp) 
        {
            UserNotifyClose();
            if (resp != null && resp.Status == "Overwrite")
            {
                UserConfirm("Exercise name already exists. Overwrite?",
                    function(okay)
                    {
                        if (okay)
                        {
                            UserNotify("Saving exercise...");
                            ajax("Handler.ashx/" + GetPath(ExerName) + "?act=saveexercise&overwrite=true&exer=" + exerState(),
                                null, 
                                function(resp) 
                                {
                                    UserNotifyClose();
                                    SaveExerciseClose()
                                }
                            );
                        }
                    });
            }
            else
                SaveExerciseClose();
        } 
    );
}
function SaveExerciseClose()
{
    UserNotifyClose();
    if (!FS.IsGuestLog())
        RefreshExerciseList();
    ClearDirty();
}

function TryOpenExercise(exername)
{
//    if (exername == null)
//        exername = GetPath(event.srcElement.innerText);
    if (Dirty)
        UserConfirm("Discard changes?", function(okay){if(okay)OpenExercise(exername);});
    else
        OpenExercise(exername);
}
function OpenExercise(exername)
{
    UserNotify("Opening exercise...");
    ajax("Handler.ashx/" + GetPath(exername) + "?act=openexercise&fs=" + FS.toString() +
        (FS.IsGuestLog() ? ("&guest=" + FS.Sub) : ""), 
        null, 
        function(resp) 
        { 
            UserNotifyClose();
            if (resp != null)
            {
                ClearExercise();
                restoreExerState(resp);
                if (FS.IsGuestLog()) // remove all add/delete image-icons from the table
                    for (var i = 0; i < exerSegs(); i++)
                    {
                        exerRow(i).cells(0).innerHTML = "";
                        exerRow(i).cells(exerRow(i).cells.length - 1).innerHTML = "";
                    }
                recalcGlobals(false, true);
//TBD? needed for external guest log, but not in the new form in FB
//                if (FS.IsGuestLog())
//                {
//                    $('LogTotalLaps').innerText = getGlobalLaps();
//                    $('LogTotalDist').innerText = getGlobalDistance();
//                    $('LogTotalTime').innerText = toHMMSS(IsSwim ? getGlobalSwimTime() : getGlobalRunTime());
//                    $('LogTotalPace').innerText = getGlobalPace();
//                    $('LogTotalRest').innerText = toMMSS(getGlobalRestTime());
//                }
                padRows();
                RedrawGraph();
                ClearDirty();
                
                if (("Refresh" in resp) && resp.Refresh)
                    RefreshExerciseList();
            }
        } 
    );
}

function LogExercise()
{
    if (IsFacebook)
    {//alert($('FriendList').innerHTML);
        var friends = eval($('FriendList').innerHTML);
        var friendsmenu = [];
        for (var i = 0; i < friends.length; i++)
            friendsmenu.push({ Text:friends[i].Name, Action:(friends[i].HasLog ? ShowFriendLog : null), Data:friends[i] });
        var menu = CreateContextMenu(friendsmenu, { x:170, y:30, height:400, width:150, bgcolor:'#caf3ff' });
    }
    else
        OpenLogDialog();
}

function ShowFriendLog()
{
    var friend = contextitem.Data;
    OpenFriendLogFolder(friend);
}
