//
// Copyright (c) 2005 Mooffie <mooffie@typo.co.il>
//
// This script may not be installed on any server
// other than that contracted by Mooffie.

dynamic_html = true;  // Does our browser support so-called "dynamic HTML"?
		      // It's determined later, in setup_form().

// E() - converts an element id to the element object.
function E(id) {
    if (document.getElementById) {
	// new browsers
        return document.getElementById(id);
    } else if (document.all) {
	// old IE
        return document.all[id];
    } else {
	// old browsers
	return find_form_element(id);
    }
}

// find_form_element() is used for finding radio buttons
// (since getElementById() is not supposed to return an array).
function find_form_element(name) {
    if (document.forms[name]) {
	return document.forms[name];
    } else {
	for (var i = 0; i < document.forms.length; i++) {
	    if (document.forms[i].elements[name])
		return document.forms[i].elements[name];
	}
    }
}

// display_factors() builds a fancy HTML string representing the factors
// and prints it out.
function display_factors(original, factors)
{
    var s = original + "   ==>   ";

    if (!factors.length || (factors.length == 1 && original.equals(factors[0]))) {
	s += msg_cannot_factor;
    } else {    
	var last_factor = "";
	var acc_deg = 1;
	array_append(factors, "");
	for (var i = 0; i < factors.length; i++) {
	    var factor = factors[i].toString();
	    if ((factors.length > 2 && / /.test(factor)) || /[\(\/]/.test(factor)) {
		// put brackets around the factor.
		if (/\/\d|\(/.test(factor))
		    factor = "[" + factor + "]"; // use bigger brackets if we have fractions.
		else
		    factor = "(" + factor + ")";
	    }
	    if (factor == last_factor) {
		// if this factor was printed already, don't
		// print it again but increase the exponent instead.
		acc_deg++;
		continue;
	    }
	    if (acc_deg > 1) {
		// Before we print the current factor we print out
		// the exponent of the previous factor.
		s += "^" + acc_deg;
		acc_deg = 1;
	    }
	    if (original.is_constant()) // a list of prime numbers
		s += "  ";
	    s += factor;
	    last_factor = factor;
	}
    }

    s = s.replace(/q/g, ch_radical);
    s = s.replace(/j/g, ch_imag);
    s = s.replace(/\+\/-/g, ch_plus_minus);
    s = s.replace(/[\{\}]/g, "");

    // I wish we could display a slanted X, as in textbooks, but
    // in Internet explorer this causes a gap between it and the exponent.
    //s = s.replace(/x/g, "<i>x</i>");

    // Convert "1/3" to "(1)/3", and "2 1/3" to "2(1)/3".
    s = s.replace(/(\d) ([\d.]+)\//g, "$1_$2/");
    s = s.replace(/(-?[\d.]+)\//g, "($1)/");
    s = s.replace(/_/g, "");

    // We use 'non-breaking' spaces for two reasons:
    // 1. To prevent word wraps (especially since we use tables).
    // 2. To preserve any " " at the edge of cells ("x + 1/2" -> "[x + ][1/2]");
    s = s.replace(/ /g, "&nbsp;");

    // We use a table because that's how we render the fractions.
    s = "<table align='center' border='0' cellspacing='0' cellpadding='0'><tr><td>" +
		s + "</td></tr></table>";
    
    // For every fraction generate a table with the numerator in the
    // upper cell and the denominator in the lower.
    s = s.replace(/\(([^\(\)]+)\)\/(-?[\d.]+)/g, "</td><td>" +
		    "<table border='0' cellspacing='0' cellpadding='0'>" +
		    "<tr><td><u>$1</u></td></tr>" +
		    "<tr><td align='center'>$2</td></tr>" +
		    "</table>" +
		    "</td><td>");

    // Note: if you change the previous HTML, make sure it doesn't contain "-"s.
    s = s.replace(/-/g, ch_minus); // use en-dash instead of the ASCII one.

    // Now we deal with exponents ("^n"). Unfortunately, we can't use
    // <sup>n</sup>, because this will cause vertical misalignment in different
    // cells of the table.
    var exponent_style = "font-size: 0.83em; position: relative; top: -0.4em;";
    s = s.replace(/\^(\d+)/g, "</td><td><span style='"+exponent_style+
                                "'>$1</span></td><td>");
   
    // Bigger brackets.
    //
    // We don't use inline style here because we need to explicitly state
    // the color of the brackets and this can only be done by the author of the
    // site, who also chooses the colors for the page.
    //
    // The big brackets should be rendered in a lighter color than the text's,
    // because using the normal color for them will make them stand out too much.
    s = s.replace(/\[/g, "</td><td><span class='big-brackets'>(</span></td><td>");
    s = s.replace(/\]/g, "</td><td><span class='big-brackets'>)</span></td><td>");

    s = s.replace(/==>/g, goes_to);
    
    E("disp").innerHTML = s;
}

function allocate_p(a, idx) {
    while (a.length <= idx)
	a[a.length] = 0;
}

// parse_polynomial() uses regular expressions to parse a polynomial string.
// it returns an array with the coefficients, or 'false' if encountered
// invalid string.
function parse_polynomial(s) {
    // First we convert all en-dashes (U+2013) to ASCII dashes.
    // The following Unicode notation ("\uxxxx") doesn't work in all browsers,
    // but at least I'm sure it can't cause fatal error.
    s = s.replace(/\u2013/g, "-");
    var pow_re = /^\s*([-+])?\s*([\d.]*)\s*\*?\s*[x|X](\d+|\s*\^\s*\d+|)/;
    var num_re =/^\s*([-+])?\s*([\d.]+)/;
    var p = [];
    var matches;
    while (1) {
	// Note: I can't just write "if (matches = pow_re.exec(s))" because
	// it seems that Netscape doesn't recognize "if (a = b)". It _does_
	// recognize "if ((a = b))", but I want to be on the safe side.
	if (pow_re.test(s)) {
	    // this is a term involving 'x'.
	    matches = pow_re.exec(s);
	    s = s.substr(matches[0].length);
	    var sign = (matches[1] == '-') ? -1 : 1;
	    var coef = (matches[2] || 1) * sign;
	    var deg  = (matches[3] || 1);
	    deg = (""+deg).replace(/^\s*\^\s*/, "") * 1;
	    if (coef != 0) {
		allocate_p(p, deg);
		p[deg] += coef;
	    }
	}
	else if (num_re.test(s)) {
	    // this is a term with a constant only.
	    matches = num_re.exec(s);
	    s = s.substr(matches[0].length);
	    var sign = (matches[1] == '-') ? -1 : 1;
	    var coef = matches[2] * sign;
	    allocate_p(p, 0);
	    p[0] += coef;
	}
	else if (/^\s*$/.test(s)) {
	    // end of string
	    break;
	}
	else {
	    // none of the above means invalid string
	    dbg("BAD{"+s+"}");
	    return false;
	}
    }
    p.reverse();
    return p;
}

function display_message(msg) {
    if (dynamic_html)
	E("disp").innerHTML = "<i>"+msg+"</i>";
    else
	prt(msg);
}

// do_factor() is the central function here. It parses
// the user input, builds a Polynomial object, query it
// for factors and prints out the results.
function do_factor()
{
    var radios = find_form_element("roots");
    for (var i = 0; i < radios.length; i++) {
	if (radios[i].checked) {
	    var kind = radios[i].value;
	    if (kind == "rational") {
		SOLVE_QUADRATIC = false;
	    } else if (kind == "complex") {
		SOLVE_QUADRATIC = true;
		SOLVE_IMAG      = true;
	    } else {
		SOLVE_QUADRATIC = true;
		SOLVE_IMAG      = false;
	    }
	}
    }

    E("output").value = "";
    var arr = parse_polynomial(E("polynomial").value);

    if (typeof(arr) != "object") {
	display_message(msg_invalid_input);
    } else if (arr.length == 0) {
	display_message(msg_blank_input);
    } else {
	var p = new Polynomial(arr);
	var factors = p.get_sorted_factors();
	prt("Factors of \"" + p + "\" are:");
	prt("");
	for (var i = 0; i < factors.length; i++) {
	    prt((i+1) + ": " + factors[i]);
	}
	if (dynamic_html) {
	    display_factors(p, factors);
	}
    }
    return true;
}

// Event handlers:

function FORM_polynomial_form_onsubmit() {
    do_factor();
    return false; // don't submit the form
}

function FORM_factor_btn_form_onclick() {
    do_factor();
    return true;
}

function FORM_example_onclick() {
    var options = E("examples").options;
    for (var i = 1; i < options.length; i++) {
	if (options[i].selected) {
	    E("polynomial").value = options[i].text.replace(/\s*(;|\/\/).*/, "");
	    E("factor-btn").onclick(); // Konqi doesn't support the 'click()' method.
	    break;
	}
    }
}

// Test browser capabilities and setup form event handlers.
function setup_form()
{
    if (!E("disp") || (typeof(E("disp").innerHTML) == "undefined")) {
	dynamic_html = false;
    } else {
	// If we use dynamic HTML, disable the ugly output box.
	E("output-form").style.display = 'none';
	E("output").style.display = 'none'; // old mozilla need this too
    }
    if (/linux|freebsd/i.test(navigator.userAgent)) {
	// I don't have fancy fonts on my Linux.
	ch_minus = "-";
    }

    // Set up event handlers.
    E("polynomial-form").onsubmit = FORM_polynomial_form_onsubmit;
    E("factor-btn").onclick = FORM_factor_btn_form_onclick;
    if (E("examples")) // in case we don't have an example menu in the page.
	E("examples").onchange = FORM_example_onclick;
}

setup_form();

display_message(msg_results_here);



