
// Big ball of procedural mess. At least there's lots of functions and
// plenty of flexibility to refactor.

// CONVENIENCE DEBUG WIDGET CONTROLS

// current question is in this variable
var curQuestion = false;

function getAnswer() {
    var raw = $('#answer').val();
    if (raw === '') { return 0; }
    return parseFloat(raw);
}

$(function () {
    // EVENT HANDLERS
    
    $('#submit').click(function (e) {
        curQuestion.animate(getAnswer());
        return false;
    });
    
    $('#reset').click(reset);
    
    $('#riverSpeed').append(riverSpeed.toString() + ' m/s');
    $('#riverWidth').append(riverWidth.toString() + ' m');
    
    var select = $('#question');
    var rawSelect = select.get(0);
    var option;
    for (var i = 1; i < questions.length; i++) {
        option = new Option('#' + i + ": " + questions[i].name, i,
            i == 1, i == 1);
        try {
            // standards compliant; doesn't work in IE
            rawSelect.add(option, null);
        } catch (e) {
            rawSelect.add(option); // IE only
        }
    }
    select.change(reset);
    
    $('#results').fadeTo('normal', 0).css('display', 'block');
    
    setupQuestion();
    loadBoatImages();
    
});

// PROGRAM CONFIGURATION VARIABLES

// conversion factor
var pixelsPerMeter = 20;
function m2px(m) { // convert meters to pixels
    return m * pixelsPerMeter;
}

// in milli-seconds
var resetTime   = 500;
var targetTime  = 400;

// pixel pre-sets
var boatWidth = 30; // see images in img/boat
var riverStart = -2000;
var riverVerticalStart = 35;
var boatStart = 80;
var buoyStart = boatStart + boatWidth / 2;
var targetStart = boatStart + 3;

// meter or m/s measurements
var riverWidth = 20;
var riverSpeed = 20;

// ANIMATION FUNCTIONS
function reset() {
    resetRiver();
    setupQuestion();
    $('#results').fadeTo('normal', 0);
    return false;
}

function displayResults() {
    var expectedAnswer = curQuestion.getAnswer();
    var givenAnswer   = curQuestion.givenAnswer;
    var error = Math.percentError(expectedAnswer, givenAnswer);
    $('#results-content').empty().append('<p>The actual answer was ' + expectedAnswer.toFixed(2) + ' ' + curQuestion.answerUnits + '; error of ' + error + '%.</p>');
    if (Math.abs(parseFloat(error)) < 5) {
        // good!
        $('#results').css('background', '#70A978');
        var curQuestionIndex = getQuestionIndex();
        if (curQuestionIndex + 1 < questions.length) {
            $('#results-content').append('<div id="next-question-container"><input id="next-question" type="submit" value="Next Question" /></div>');
            $('#next-question').click(function (e) {
                $('#question').val(curQuestionIndex + 1);
                reset();
            });
        } else {
            $('#results-content').append('<div id="next-question-container"><strong>Done!</strong></div>');
        }
    } else {
        // bad!
        $('#results').css('background', '#A97084');
    }
    $('#results').fadeTo('normal', 1);
}

var alreadyAnimated = false;

function animateRiver(boatTravels, time) {
    var riverTravels = m2px(time * riverSpeed);
    var animateTime  = time * 1000;
    $('#river').animate({left: riverStart + riverTravels}, animateTime, 'linear',
        function () {$("#reset").removeAttr('disabled');displayResults();}); // callback
    $('#buoy').animate( {left: buoyStart  + riverTravels},  animateTime, 'linear');
    $('#boat').animate( {left: boatStart  + m2px(boatTravels), top: riverVerticalStart + m2px(riverWidth)}, animateTime, 'linear');
    $('#reset,#submit,#answer').attr('disabled', 'disabled');
    alreadyAnimated = true;
}

function resetRiver() {
    if (!alreadyAnimated) {
        $("#reset,#submit,#answer").removeAttr('disabled');
        return;
    }
    $('#river').animate({left: riverStart}, resetTime,
        function () {$("#reset,#submit,#answer").removeAttr('disabled');}); // callback
    $('#buoy').animate( {left: buoyStart},  resetTime);
    $('#boat').animate( {left: boatStart, top: riverVerticalStart}, resetTime);
    $('#reset').attr('disabled', 'disabled');
    alreadyAnimated = false;
}

function rotateBoat(angle) {
    // round to nearest five
    angle = Math.round(angle / 5) * 5;
    var prefix;
    if (angle > 0) {
        prefix = 'd_';
    } else if (angle == 0) {
        prefix = '';
    } else {
        prefix = 'u_';
    }
    angle = Math.abs(angle);
    var suffix;
    if (angle < 10) {
        suffix = '0' + angle.toString();
    } else {
        suffix = angle.toString();
    }
    var filename = 'img/boat/' + prefix + suffix + '.gif';
    $('#boat').attr('src', filename);
}

function loadBoatImages() {
    // image preloader function
    var i = function (name) {
        var i = new Image();
        i.src = 'img/boat/' + name + '.gif';
    };
    i('00');
    i('d_05'); i('u_05');
    var suffix;
    for (var angle = 10; angle <= 90; angle += 5) {
        suffix = angle.toString();
        i('d_' + suffix);
        i('u_' + suffix);
    }
}

// QUESTIONS

function getQuestionIndex() {
    return parseInt($('#question').val(), 10);
}

function setupQuestion() {
    var factory = questions[getQuestionIndex()];
    curQuestion = factory.generate();
    curQuestion.prepare();
    $('#boatSpeed').empty().append(curQuestion.boatSpeed + ' m/s');
    $('#desc').empty().append(curQuestion.getQuestion());
    var answer = $('#answer');
    answer.unbind();
    answer.keyup(curQuestion.answerHandler);
    curQuestion.answerHandler();
}

// namespace for questions
var Question = {};

Question.FindDistance = function (angle) {
    if (typeof angle !== 'undefined') {
        this.angle = angle;
        if (angle === 0) {
           this.name  = 'Find Distance With No Angle';
        }
    }
};
Question.FindDistance.prototype = {
    name: 'Find Distance',
    angle: false,
    boatSpeed: false,
    answer: false,
    givenAnswer: false,
    answerUnits: 'm',
    generateBoatSpeed: Question.generateBoatSpeed,
    generate: function () {
        return new Question.FindDistance(this.angle);
    },
    getQuestion: function () {
        var absAngle = Math.abs(this.angle).toString();
        if (this.angle == 0) {
            return "How far downstream will a boat aimed straight across the river land?";
        } else if (this.angle > 0) {
            return "How far downstream will a boat aimed " + absAngle + " degrees downstream land?";
        } else {
            return "How far downstream will a boat aimed " + absAngle + " degrees upstream land?";
        }
    },
    getAnswer: function () {
        if (this.answer === false) { this.animate(); }
        return this.answer;
    },
    prepare: function () {
        // generate question parameters
        this.boatSpeed = Math.randomInt(5, 40);
        var sign;
        if (this.angle === false) {
            sign = Math.randomInt(0, 1) ? -1 : 1;
            this.angle = sign * Math.randomInt(5, 45);
        }
        rotateBoat(this.angle);
        $('#answer-units').empty().append('m');
        var answer = $('#answer');
        answer.unbind();
        answer.keyup(this.answerHandler);
        this.answerHandler();
        // answer.keyup(function (){setTimeout(this.answerHandler, 700);}) // doesn't work...
    },
    animate: function (answer) {
        this.givenAnswer = answer;
        var rad = this.angle * Math.RpD;
        var time = riverWidth / (Math.cos(rad) * this.boatSpeed);
        this.answer = (this.boatSpeed * Math.sin(rad) + riverSpeed) * time;
        animateRiver(this.answer, time);
    },
    answerHandler: function () {
        $('#target').animate({left: targetStart + m2px(getAnswer())}, targetTime);
    }
};

Question.FindAngle = function (distance) {
    if (typeof distance !== 'undefined') {
        this.distance = distance;
        if (distance === 0) {
           this.name  = 'Find Angle For No Drift';
        }
    }
}
Question.FindAngle.prototype = {
    name: 'Find Angle',
    distance: false,
    boatSpeed: false,
    answer: false,
    givenAnswer: false,
    answerUnits: 'degrees',
    generateBoatSpeed: Question.generateBoatSpeed,
    generate: function () {
        return new Question.FindAngle(this.distance);
    },
    getQuestion: function () {
        if (this.distance === 0) {
            return "At what angle downstream should the boat be aimed so that it can cross the river with no drift?";
        } else {
            return "At what angle downstream should the boat be aimed to reach a point on the shore " + this.distance + " m downstream?";
        }
    },
    getAnswer: function () {
        if (this.answer === false) this.animate();
        return this.answer;
    },
    prepare: function () {
        // generate question parameters
        // the algorithm here is a little questionable: is it
        // acceptable for us to stipulate negative distances?
        if (this.distance === false) {
            this.distance = Math.randomInt(2, 40);
        }
        this.boatSpeed = Math.randomInt(riverSpeed + 5, 45);
        $('#target').animate({left: targetStart + m2px(this.distance)}, targetTime);
        $('#answer-units').empty().append('degrees');
    },
    animate: function (answer) {
        this.givenAnswer = this.angle = answer;
        var rad = this.angle * Math.RpD;
        var time = riverWidth / (Math.cos(rad) * this.boatSpeed);
        var givenDistance = (this.boatSpeed * Math.sin(rad) + riverSpeed) * time;
        animateRiver(givenDistance, time);
        
        var rad2 = Math.atan(riverWidth / this.distance);
        var rad3 = Math.PI / 2 - rad2;
        var rad4 = Math.asin(riverSpeed * Math.sin(rad2) / this.boatSpeed);
        this.answer = (rad3 - rad4) / Math.RpD;
    },
    answerHandler: function () {
        rotateBoat(parseFloat(getAnswer()));
    }
};

// array of all available questions
var questions = [null,
    new Question.FindDistance(0),
    new Question.FindAngle(0),
    new Question.FindDistance(),
    new Question.FindAngle()
];
