/**
 * Verification Process
 *  - Launch JavaVersion applet
 */

var pems = {};
var verificationId = null;
var verificationSuccessfulCallback = null;
var testContext = new TestContext();
var tests = new Array();
var completedTestMap = {};

var prelimiaryTestWarnings = false;
var testWarnings = false;

/**
 * Called once the dom is ready
 */
function onReady() {
    pems.versionAppletContainer = $("#versionAppletContainer").get(0);
    pems.verifierAppletContainer = $("#verifierAppletContainer").get(0);

    pems.startButton = $("#startTestsButton").get(0);
    pems.runningButton = $("#runningTestsButton").get(0);
    pems.instructionsContainer = $("#instructions").get(0);

    pems.startScreen = $("#preStartScreen").get(0);
    pems.automatedTestResultsScreen = $("#runningFinishedAutomatedScreen").get(0);
    pems.previewViewerScreen = $("#previewViewerScreen").get(0);
    pems.previewSucceededScreen = $("#previewSucceededScreen").get(0);
    pems.previewFailedScreen = $("#previewFailedScreen").get(0);

    pems.pretestStateElement = $("#testPRELIMINARY td.state").get(0);
    pems.previewStateElement = $("#testPREVIEW td.state").get(0);

    pems.fixErrorsMessage = $("#fixErrorsAboveMessageContainer").get(0);
    pems.startPreviewMessage = $("#startPreviewTestContainer").get(0);

    pems.previewViewerContainer = $("#previewViewerContainer").get(0);

    pems.showHideDiagnosticsButton = $("#showHideButton").get(0);
    pems.diagnosticContainer = $("#diagnosticInformationContainer").get(0);
    pems.diagnosticTextArea = $("#diagnosticInformation").get(0);

    pems.selectedAnimationRedirectContainer = $("#selectedAnimationRedirectContainer")
    pems.exploreRedirectContainer = $("#exploreRedirectContainer")

    $(pems.showHideDiagnosticsButton).toggle(function() {$(pems.diagnosticContainer).fadeIn(500)}, function() {$(pems.diagnosticContainer).fadeOut(500)});
    
    clearVerifierPageElements();

    // autostart the verification
    startPreliminaryTest();
}

function clearVerifierPageElements() {
    pems.versionAppletContainer.innerHTML = "";
    pems.verifierAppletContainer.innerHTML = "";
    pems.previewViewerContainer.innerHTML = "";
    pems.diagnosticTextArea.value = "";
}

function startPreliminaryTest() {
    startRunningAnimation();
    
    setTimeout(startPreliminaryTestInternal, 500); // do this outside of the loading thread  
}

/**
 * Starts the preliminary test case, this test case does the following:
 *
 *  * failsafe detect of java version
 *  * failsafe detect of browser version
 *  * detecting of previous failures and reporting them
 *
 */
function startPreliminaryTestInternal() {
    logInfoMessage("Starting Verfier Tests " + new Date().toDateString());
    logInfoMessage("Client ID " + getCookie("client_id"));

    var preliminaryTest = new TestPreliminary("PRELIMINARY");

    preliminaryTest.setContext(testContext);
    preliminaryTest.loggingTargetInfoFunction = logDiagnosticMessage;

    preliminaryTest.setResultListener({
        testSuccessful : function(event) {
            callbackSuccessfulTestResults(preliminaryTest.id, event);

            continueAfterPreliminaryTest()
        },

        testFailed : function(event) {
            callbackFailedTestResults(preliminaryTest.id, event);

            continueAfterPreliminaryTest()
        }
    })

    preliminaryTest.setWarningListener({
        warning: function(warning) {
            prelimiaryTestWarnings = true;

            handlePreliminaryWarnings(warning);
        }
    });

    try {
        preliminaryTest.start();
    } catch(e) {
        callbackFailedTestResults(preliminaryTest.id, {params: {}});

        logDiagnosticMessage("Exception calling PreliminaryTest.start: " + e.name + ", " + e.message);
        continueAfterPreliminaryTest()
    }
}

function completePreliminaryTest() {
    $(pems.pretestStateElement).html("OK");
}

function continueAfterPreliminaryTest() {
    completePreliminaryTest();

    if(! prelimiaryTestWarnings)
        setTimeout(startVerification, 500);
    else
        stopRunningAnimation();
}

function startVerification() {
  pems.startButton.innerHTML = "Running...";
  tests.push(new TestUnity("UNITY"));
  startTests();
}

function startPreviewTestAutomatic() {
    switchScreen(pems.startScreen, pems.previewViewerScreen);
    startPreviewTest();
}

function startPreviewTestManual() {
    switchScreen(pems.automatedTestResultsScreen, pems.previewViewerScreen);
    startPreviewTest();
}

function startPreviewTest() {
  testContext.recordTestStart("PREVIEW")

  $(pems.previewStateElement).html("Running")

  var params = {
    id : "sampleviewer",
    width : "450px",
    height : "220px",

    archive: "/jars/json_090521.jar, /jars/commons-logging-1.1.1.jar, /jars/loader-applet-0.0.1.jar",

    amMotionId : 1,
    amCharacterType : "skin",    // character type
    amMotionTargetId : skinId, // character type id
    amMotionTargetPayload : externalId,  // external id used to download skin/resource
    amFrameRate : 30,
    amStartingFrame : 0,
    amLength :        300, // one loop, 0 - 20
    amSliderValues : "",

    amCameraMode: "following",

    viewerResourcesUrl : "/clients/00.00.03",
    viewerVersionXmlUrl : "/clients/00.00.03/version.xml",

    "motionurl-stream": "http://${host}:${port}/download/stream",
    "motionurl-preview": "http://${host}:${port}/download/pp",

    amIsLoopClip: true,
    amHttpCookies: document.cookie ,
    talkbackUrl: "/talkback/submit", // url to submit talkback requests to
    talkbackSubmitLogFiles: Mixamo.enable_log_collection(), // should we submit old log files found on startup?
    talkbackSubmitLogging: Mixamo.enable_log_collection() // should we submit runtime logging messages?
  };

  // launch the unity viewer
  Mixamo.Unity.WriteUnity( pems.previewViewerContainer, {
    width: "450",
    height: "220"
  }, params);

}

function startTests() {
    startRunningAnimation();

    ajaxCallStart();

    continueTests();
}

var animationStart;
var animationId;

function startRunningAnimation() {
    animationStart = new Date().getTime();
    animationId = setInterval(updateRunningAnimation, 200);

    $(pems.startButton).hide();
    $(pems.runningButton).show();
}

function updateRunningAnimation() {
    var diff = new Date().getTime() - animationStart;
    var mod = Math.round(diff / 750) % 4;

    var suffix = "";
    for(var i=0; i < mod; i++) {
        suffix += ".";
    }

    pems.runningButton.innerHTML = "Running" + suffix;
}

function stopRunningAnimation() {
    clearInterval(animationId);

    $(pems.startButton).show();
    $(pems.runningButton).hide();
}

function ajaxCallStart() {
    $.ajax({
        url : "/verifier/callback_start/" + verificationId,
        dataType: "json",
        success: function(data) {logDiagnosticMessage("Verification ID " + data.id)}
    })
}

function continueTests() {
    // we use setTimeout here because otherwise everything calls everything else (in one very long call stack)...
    // this breaks each test up into its own call stack
    setTimeout(continueTestsInternal, 1000);
}

var testIndex = 0;
var currentTestName = null;
var fiveServiced = 0;
function continueTestsInternal() {
    if (testIndex < tests.length) {
        var test = tests[testIndex++]
        
        try {
            test.setContext(testContext);
            test.loggingTargetInfoFunction = logDiagnosticMessage;

            test.setProgressListener(function(event) {
                onTestProgress(event)
            })

            test.setResultListener({
                testSuccessful : function(event) {
                    onTestSuccessSafe(event, test)
                },

                testFailed : function(event) {
                    onTestFailureSafe(event, test)
                }
            })

            test.setWarningListener({
                warning: function(warning) {
                    onTestWarning(warning.code, warning.params, test);
                }
            })

            onTestStart(test.id);
            test.start();
        } catch (e) {
            onTestFailureSafe(new TestFailureEvent("START", "EXCEPTION", {exception : e}), test)
        }
    } else {
        onAllTestsPass();
    }
}


/**
 * Called by the main test loop before an individual test starts.
 * 
 * @param testId
 */
function onTestStart(testId) {
    currentTestName = testId;

    testContext.recordTestStart(testId)

    logInfoMessage("Starting verifier test named: " + currentTestName);

    getTestStateElement().html("Running")
    getTestProgressElement().html(0 + "%");
}

function onTestProgress(event) {
    getTestProgressElement().html(event.percentage + "%");
}

function onTestSuccessSafe(event, test) {
    try {
        onTestSuccess(event, test);
    } catch(e) {
        logDiagnosticMessage("Exception in onTestSuccess: e.name: " + e.name + " e.message: " + e.message);
        throw e;
    }
}


/**
 * Called by the main test loop when an individual test finishes successfully.
 *
 * @param event
 */
function onTestSuccess(event, test) {
    testContext.recordTestResult(true, null);

    var paramsString = paramsToString(event.params);

    getTestStateElement().html("OK")
    getTestMessageElement().html(paramsString)

    callbackSuccessfulTestResults(currentTestName, event);

    onTestCompleted(test, event);

    continueTests();
}

function onTestFailureSafe(event, test) {
    try {
        onTestFailure(event, test);
    } catch(e) {
        logDiagnosticMessage("Exception in onTestFailure: e.name:" + e.name + " e.message: " + e.message);
        throw e;
    }
}


/**
 * Called by the main test loop when an individual test finished with failure
 *  
 * @param event
 */
function onTestFailure(event, test) {
    var paramsString = paramsToString(event.params);
    logDiagnosticMessage("onTestFailure for test " + test.id + " code: " + event.code + " params: " + paramsString);

    testContext.recordTestResult(false, event.code);
    
    getTestStateElement().html("Failed")
    getTestMessageElement().html(event.step + " -- " + event.code + " -- " + paramsString)

    logDiagnosticMessage("TEST STEP FAILED {" + currentTestName + "} reporting params " + paramsString);

    callbackFailedTestResults(currentTestName, event);

    onTestCompleted(test, event)

    onTestsFailure(event);
}

function onTestWarning(code, params, test) {
    testWarnings = true; // flag that there was a warning, so that the warning message is displayed

    var messageElement = $(".WARNING_" + code);

    messageElement.slideDown(500);
    substituteInElement(messageElement, params);
}

function handlePreliminaryWarnings(warning) {
    var messageToShow = "OTHER"

    var elem = $(".PRELIMINARY_WARNING_" + messageToShow);

    elem.slideDown(500);
    substituteInElement(elem,  warning.params);
}

/**
 * Calls back to webapp, reporting successful test results
 * 
 * @param test
 * @param event
 */
function callbackSuccessfulTestResults(test, event) {
    callbackTestResults(true, test, paramsToString(event.params))
}

/**
 * Calls back to webapp, reporting failure test results
 * @param test
 * @param event
 */
function callbackFailedTestResults(test, event) {
    callbackTestResults(false, test + "-" + event.step, paramsToString(event.params))
}

/**
 * Calls back to webapp, reporting test step result
 * 
 * @param success
 * @param name
 * @param message
 */
function callbackTestResults(success, name, message, completeCallback) {
    $.ajax({
        url : "/verifier/callback_result/" + verificationId,
        data : {
            success : success,
            name : name,
            message : message
        },
        complete: completeCallback
    })
}

var successCalled = false;
function firePreviewTestSuccess() {
    // block multiple calls
    if(successCalled)
        return;
    else
        successCalled = true;

    testContext.recordTestResult(true, null);

    // handle autoredirect logic
    var redirectContainer = (redirectDestination) ? pems.selectedAnimationRedirectContainer : pems.exploreRedirectContainer;
    var redirectUrl = (redirectDestination) ? redirectDestination : "/search";

    callbackTestResults(true, "PREVIEW", "", function() {
        reportVerificationSuccess(function() {
            window.location.href = redirectUrl;
        });
    });

    setTimeout(function() {
        $(pems.previewStateElement).html("OK")
        switchScreen(pems.previewViewerScreen, pems.previewSucceededScreen);
        _redirectTimeoutHandler(8, redirectContainer, redirectUrl);
    }, 2000);

}

function _redirectTimeoutHandler(seconds, container, url) {
    if(seconds == 0)
        window.location.href = url;
    else {
        $(container).show();
        $(".redirectIn", container).html(seconds);
        setTimeout(_redirectTimeoutHandler.bind(this, --seconds, container, url), 1000);
    }
}

function firePreviewTestFailed() {
    testContext.recordTestResult(false, "PREVIEW");

    callbackTestResults(false, "PREVIEW", "");
    reportFinished(false);

    $(pems.previewStateElement).html("Failed")
    switchScreen(pems.previewViewerScreen, pems.previewFailedScreen);
}

function logDiagnosticMessage(msg) {
    msg = "* " + msg + "\n";

    pems.diagnosticTextArea.value += msg;

    $.ajax({
        url : "/talkback/submit",
        data : {
            log : verificationLogName,
            messages: msg
        }
    })
}

/**
 * Called a test failed.  This represents the end of the testing flow
 *
 * @param event
 */
function onTestsFailure(event) {
    var messageDetails = determineFailureMessageDetails(currentTestName, event);

    var displayElement = $("#notificationContainer .FAILURE_" + messageDetails.elementNameSuffix)
    displayElement.slideDown(500);

    substituteInElement(displayElement, messageDetails.params);

    $(pems.fixErrorsMessage).show();

    switchScreen(pems.startScreen, pems.automatedTestResultsScreen);

    onCompleted();
}

/**
 * Substitutes the content of elements found in the specified element with classes matching a name/value pair
 * found in the supplied params.
 * 
 * @param element
 * @param params
 */
function substituteInElement(element, params) {
    for (var name in params ) {
        var value = params[name];

        $("." + name, element).html(value);
    }
}

function determineFailureMessageDetails(testName, event) {
    var code = event.getCode()
    var step = event.getStep()
    var params = event.getParams();

    Mixamo.console.info("Determining error message for " + code + ", " + step)

    if (step == "START" && code == "EXCEPTION") {
        return {elementNameSuffix : "OTHER", params : params}
    } else if (step == "UNITY" && code == "PLUGIN_NOT_DETECTED") {
        return {elementNameSuffix : "UNITY_PLUGIN_NOT_DETECTED", params : params};
    } else if ((step == "LAUNCH" && code == "APPLET") // java related failures
            || (step == "CHECKJAVA" && code == "UNSUPPORTED")) {

        window.location.href = "/verifier/needjava?destination=" + escape(window.location)
        
        return {elementNameSuffix : "JAVA", params : params}
    } else if (step == "LAUNCHSIGNED"
            || Mixamo.Array.contains(["CHECKPROPS", "CHECKREADWRITE", "CHECKCONNECTION"],step)) {
        return {elementNameSuffix : "LAUNCHSIGNED", params : params}
    } else if (step == "CHECKJAVA" && code == "KNOWN_COMPAT_ISSUE") {
        return {elementNameSuffix: "KNOWN_JAVA_COMPAT_ISSUE", params: params}
    } else if (code == "FF35OLDJAVA") {
        return {elementNameSuffix: "FF35OLDJAVA", params: params}
    } else if (step == "CHECKBROWSER" && code == "UNSUPPORTED") {
        return {elementNameSuffix : "BROWSER", params : params}
    } else if (step == "CHECKOS" && code == "UNSUPPORTED") {
//         return { elementNameSuffix : "WINDOWS", params : {osversion : params.os }.hashmerge(params) }
         return {elementNameSuffix : "WINDOWS", params : {osversion : params.os}}

    } else if (step == "CHECKDIRECTX" && code == "EXCEPTION") {
         return {elementNameSuffix : "DIRECTX", params : params}

    } else {
        return {elementNameSuffix : "OTHER", params : params}
    }
}

function onAllTestsPass() {
    $(".SUCCESS").slideDown(500);
    $(pems.startPreviewMessage).show();
    $(pems.previewStateElement).html("Pending")

    onCompleted();

    if(verificationSuccessfulCallback)
        verificationSuccessfulCallback();

    if(!testWarnings) {
        startPreviewTestAutomatic();
    } else {
        switchScreen(pems.startScreen, pems.automatedTestResultsScreen);
    }
}

/**
 * Callback to the webapp that the verification process has completed successfully
 */
function reportVerificationSuccess(completeCallback) {
    reportFinished(true, completeCallback);
}

/**
 * Calls HTTP callback on server with verifier result(s).
 * @param success
 */
function reportFinished(success, completeCallback) {
    $.ajax({
        url : "/verifier/callback_finished/" + verificationId,
        data : {
            success : success
        },
        complete: completeCallback
    })
}

/**
 * Fades out of one screen and displays the other
 * 
 * @param element1
 * @param element2
 */
function switchScreen(element1, element2) {
    $(element1).fadeOut(500, function() {$(element2).fadeIn(200)});
}

/**
 * Actions that are to be performed after either success or failure
 */
function onCompleted() {
    stopRunningAnimation();
}

/**
 * Called on success or failure
 */
function onTestCompleted(test, event) {
    // complain if we've already seen a response for this specific test
    if(completedTestMap[test.id])
        logDiagnosticMessage("Already received completion event for test " + test.id);

    completedTestMap[test.id] = event;
    
    getTestProgressElement().html("100%");
}

function getTestSelector() {
    return "#test" + currentTestName;
}

function getTestStateElement() {
    return $(getTestSelector() + " .state")
}

function getTestMessageElement() {
    return $(getTestSelector() + " .message");
}

function getTestProgressElement() {
    return $(getTestSelector() + " .progress")
}

$(document).ready(function() {
    onReady();
});

function viewerNotificationJSONString(notificationJson) {
    viewerNotification(JSON.parse(notificationJson));
}

function viewerNotification(notification) {
    setTimeout(function() { // execute in timeout thread to ensure that JS remains single threaded

        _viewerNotification(notification);

    }, 50); // wait 50 millis, otherwise things seem to still be in a race condition
}

function _viewerNotification(notification) {
    try {
		if (notification.level == "fatal")
			console.error("Error notification from the viewer");
		if (notification.level == "warning")
			console.warn("Warning notification from the viewer");
		if (notification.level == "info")
			console.info("Info notification from the viewer");

		console.group("Notification Details");
		console.dir(notification);
		console.groupEnd();
    } catch(e) {
        // hahahahahahahhaha
    }
}

function viewerProgressEvent() {}
function viewerEventPlay() {}
function viewerUpdateClipBounds() {}
function downloadProgressEvent() {}
function viewerStreamEvent() {}


function getCookie(c_name)
{
if (document.cookie.length>0)
  {
  c_start=document.cookie.indexOf(c_name + "=");
  if (c_start!=-1)
    {
    c_start=c_start + c_name.length+1;
    c_end=document.cookie.indexOf(";",c_start);
    if (c_end==-1) c_end=document.cookie.length;
    return unescape(document.cookie.substring(c_start,c_end));
    }
  }
return "";
}

function submitEmail(element, destinationAddress, subject, emailBody) {
    var e = $(element);

    emailBody = "AUTOMATED EMAIL ON BEHALF OF THE USER\n\n" + emailBody;

    e.html("Sending...");

    $.ajax({
        url : "/support/email",
        type: "POST",
        data : {
            authenticity_token: AUTH_TOKEN,
            subject : subject,
            body : emailBody
        },
        complete: function() {e.html("Sent!")}
    })
}

function isBuggyJavaUnload() {
    BrowserDetect.init();
    return BrowserDetect.browser == "Chrome";
}

function paramsToString(val) {
    return toString(val);
}

function logInfoMessage(msg) {
    logDiagnosticMessage("LOG: " + msg)
    Mixamo.console.info(msg);
}

//
// cookie methods, thanks http://www.quirksmode.org/js/cookies.html
//

function createCookie(name,value,days) {
	Mixamo.createCookie(name,value,days);
}

function readCookie(name) {
    Mixamo.readCookie(name);
}

function eraseCookie(name) {
	Mixamo.eraseCookie(name);
}


/*
    TEST HELPER METHODS
 */

if(typeof MIXAMO_SELENIUM_HELPER == "undefined")
    MIXAMO_SELENIUM_HELPER = {}

VERIFIER_TEST_HELPER = MIXAMO_SELENIUM_HELPER

extend(VERIFIER_TEST_HELPER, {
    isTestFailed: function(testname, code) {
        if(VERIFIER_TEST_HELPER.isTest(testname)) {
            var state = testContext.getVerificationState()
            var test_failed = typeof state[TestContext.SUCCESS_KEY] != "undefined" && false == state[TestContext.SUCCESS_KEY];

            // test if the failure code was the one specified
            if(code) {
                return test_failed && state[TestContext.CODE_KEY] == code
            } else {
                return test_failed
            }
        }

        return false;
    },

    /**
     * Returns true if the specified test is the test that is running/passed/or failed
     * @param testname
     */
    isTest: function(testname) {
        if(testContext) {
            var state = testContext.getVerificationState()
            if(state) {
                return state[TestContext.TEST_KEY] && testname == state[TestContext.TEST_KEY];
            }
        }

        return false;
    }
})

function getPrettyDateString() {
    var now = new Date();

    var year = now.getFullYear();
    var month = now.getMonth() + 1;
    var date = now.getDate();
    var hour = now.getHours();
    var minutes = now.getMinutes();
    var seconds = now.getSeconds();

    var yearString = year.toString()
    var monthString = (month < 10) ? "0" + month.toString() : month.toString();
    var dateString = (date < 10) ? "0" + date.toString() : date.toString();
    var hourString = (hour < 10) ? "0" + hour.toString() : hour.toString();
    var minutesString = (minutes < 10) ? "0" + minutes.toString() : minutes.toString();
    var secondsString = (seconds < 10) ? "0" + seconds.toString() : seconds.toString();

    return yearString + monthString + dateString + "_" + hourString + minutesString + secondsString + "000";
}

