// Flashcard App v 3-01
//
// Copyright 2009, Bryan Abshier
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Resources used:
// (JSDG) JavaScript the Definitive Guide 5th ed
//
// Conventions:
// String flags are either "yes" or "no"
//

// Global Constants
var BACKEND="http://teishoku.org/oroka/sogapi.pl";

// Toolbar object
//
// on: "yes" or "no" ; Display this item in the toolbar?
// name: The name to be displayed on the toolbar.
// fname: The name of the javascript function link. (don't forget the '()');
// launch: A launcher for the hotkey so I don't have to use an eval.
// alias: an alternate hotkey, maps to the key it's an alias for
//
var tb = {};
tb.f = { on:"no", name:"Flip", fname:"flipCard()", launch: function() { flipCard() } };
tb.space = { alias:"f" };
tb.return = { alias:"f" };
tb.n = { on:"no", name:"Next", fname:"nextCard()", launch: function() { nextCard() } };
tb.right = { alias:"n" };
tb.l = { on:"no", name:"Last", fname:"lastCard()", launch: function() { lastCard() } };
tb.left = { alias:"l" };
tb.e = { on:"no", name:"Learn", fname:"learnCard()", launch: function() { learnCard() } };
tb.down = { alias:"e" };
tb.r = { on:"no", name:"Direction", fname:"cardDirection()", launch: function() { cardDirection() } };
tb.m = { on:"no", name:"Remove", fname:"removeCard()", launch: function() { removeCard() } };
tb.up = { alias:"m" };
tb.s = { on:"yes", name:"Select", fname:"selCardsReset()", launch: function() { selCards() } };
tb.i = { on:"no", name:"Log In", fname:"logIn()", launch: function() { logIn() } };
tb.o = { on:"no", name:"Log Out", fnmae:"logOut()", launch: function() { logOut() } };
tb.d = { on:"no", name:"Load", fname:"loadSet()", launch: function() { loadSet() } };
tb.v = { on:"no", name:"Save", fname:"saveSet()", launch: function() { saveSet() } };
tb.g = { on:"no", name:"Get Cards", fname:"getCards()", launch: function() { viewCards() } };
tb.t = { on:"no", name:"Return", fname:"goBack()", launch: function() { goBack() } };
tb.a = { on:"no", name:"Main", fname:"mainMenu()", launch: function() { mainMenu() } };
tb.h = { on:"no", name:"Reshuffle", fname:"reShuffle()", launch: function() { mainMenu() } };


// Translation table for key codes used by the key event handler.
// Maybe I can limit my key map to keys which might actually be pressed?
var keyMap = {
    8:"backspace", 9:"tab", 13:"return", 19:"pause", 27:"escape", 32:"space",
    33:"pageup", 34:"pagedown", 35:"end", 36:"home", 37:"left", 38:"up",
    39:"right", 40:"down", 45:"insert", 46:"delete", 48:"0", 49:"1", 50:"2",
    51:"3", 52:"4", 53:"5", 54:"6", 55:"7", 56:"8", 57:"9", 59:";", 61:"=",
    65:"a", 66:"b", 67:"c", 68:"d", 69:"e", 70:"f", 71:"g", 72:"h", 73:"i",
    74:"j", 75:"k", 76:"l", 77:"m", 78:"n", 79:"o", 80:"p", 81:"q", 82:"r",
    83:"s", 84:"t", 85:"u", 86:"v", 87:"w", 88:"x", 89:"y", 90:"z", 107:"+",
    109:"-", 110:".", 188:","
}

// Set up sreq
var sreq = new XMLHttpRequest();
// XML object for the card-set index retreived from the server. (JSDG 503)
var cardex = document.implementation.createDocument("", "", null);
var cardsel = []; // "yes"/"no" Array for selected cards-sets.
var csseld = 0; // The number of card sets currently selected
// The object for card data.
var card = {};
// This will be extended with card[n] = {}, then
var cardRef = []; // The order in which to present cards
var cardCurrent = -1; // The number of the next card
var cardPart = 0; // Which side is currently being shown
var cardDir = 1;  // Which direction cards are being flipped (1/-1)
var cardDirRandom = 0;  // Set the flip direction 0=fwd,1=rnd,2=bck
var cardCN = "no"; // Call nextCard after we get data?
var clearStat = "yes";
// Variables which are local to a function, but need to persist over sucsessive calls.
selCards.loop = 0;

// Clearit: Remove all of the children from an element
function clearIt(eid) {
    var cell = document.getElementById(eid);
    while (cell.hasChildNodes()) { cell.removeChild(cell.firstChild) }
}

// Put the time in the status bar
function statClock() {
    var newt = document.getElementById('clock');
    newt.innerHTML = "test";
    var d = new Date();
    var ap = " am";
    var hour = d.getHours();
    if (hour > 12) {
	hour -= 12;
	ap = " pm";
    }
    var min = d.getMinutes();
    if (min < 10) { min = "0"+min }
    newt.innerHTML = hour+":"+min+ap;
}

function clearId(x) {
    var newm = document.getElementById(x);
    newm.innerHTML = '';
}

// Print a message in the status line
function statMess(text,other) {
    var x = 'status';
    if (other != undefined) { x = other }
    else { clearStat = "no" }
    var newm = document.getElementById(x);
    newm.innerHTML = text+" | ";
}

// Add a new child <p> element in the header
function inHeader(text,cls,id) {
    var newp = document.createElement('p');
    if (cls != null) { newp.setAttribute('class',cls) }
    if (id != null) { newp.setAttribute('id',id) }
    newp.innerHTML=text;
    document.getElementById('header').appendChild(newp);
}

// Add a new child <p> element in the cardbox (with class cls.)
function innerPs(text,cls,id) {
    var newp = document.createElement('p');
    if (cls == undefined) { cls = 'line' }
    newp.setAttribute('class',cls);
    if (id != undefined) { newp.setAttribute('id',id) }
    newp.innerHTML=text;
    document.getElementById('cbox').appendChild(newp);
}

// Display the toolbar from the global tb object
function showToolbar() {
    var pl= '';
    var test1 = '';
    var test2 = '';
    var holder = '';
    var occur = '';
    var x = '';

    var newt = document.getElementById('toolbar');

    for (x in tb) {
	if (tb[x].on == "yes" && tb[x].alias == undefined) {
	    test1 = tb[x].name.toLowerCase();
	    test2 = x.toLowerCase();
	    occur = test1.indexOf(test2);
	    if (occur >= 0) { holder = tb[x].name.slice(0,occur)+'<span class=underline>'+tb[x].name.slice(occur,occur+1)+'</span>'+tb[x].name.slice(occur+1) }
	    else { holder = tb[x].name }
	    pl = pl+'<a href="javascript:'+tb[x].fname+'">'+holder+'</a> | ';
	}
    }
    pl = pl.slice(0,pl.length-2);
    newt.innerHTML=pl;
}

// Toolbar setup when viewing card data
function cardToolbar() {
    tb.f.on = "yes";
    tb.n.on = "yes";
    tb.l.on = "yes";
    tb.r.on = "yes";
    tb.m.on = "yes";
    tb.e.on = "yes";
    tb.g.on = "no";
    tb.t.on = "no";
    tb.s.on = "no";
    tb.a.on = "yes";
    tb.h.on = "no";
    showToolbar();
}
	 
function menuToolbar() { // Now for the Main menu 
    tb.f.on = "no";
    tb.n.on = "no";
    tb.l.on = "no";
    tb.r.on = "no";
    tb.m.on = "no";
    tb.e.on = "no";
    tb.s.on = "yes";
    tb.g.on = "no";
    tb.h.on = "yes";
    tb.a.on = "no";
    if (csseld > 0) { tb.t.on = "yes" } 
    showToolbar();
}

// Show some testing text in the cardbox
function testCardInc(text) {
    var i = 0;

    if (testCardInc.counter > 7) {
	testCardInc.counter = 0;
	clearIt("cbox");
    }
    innerPs(text+testCardInc.counter);
    testCardInc.counter++;
}

// Obtain all text from all children beneath a givin DOM node. (JSDG p319)
function getText(n)  {
    var strings = [];
    getStrings(n, strings);
    return strings.join("");
    
    function getStrings(n, strings) {
	if (n.nodeType == 3) { // for a text node
	    strings.push(n.data);
	}
	else if (n.nodeType == 1) { // for an element node
	    for (var m = n.firstChild; m != null; m = m.nextSibling) {
		getStrings(m, strings);
	    }
	}
    }
}

// Main menu space
function mainMenu() {
    menuToolbar();
    clearIt("cbox");
}    

// After we get the cards from the server, we can actually select them.
function selCards(x) {
    var LIST = 12;  // How many cards do we list at a time
    var i = 0; // an indexer
    var lb = 0; // lower bound
    var ub = 0; // upper bound
    var selbar = ''; // a string for the selection bar
    var titem = ''; // a string for card item titles
    var rtext = '';
    var stext = '';

    // Clear the status line
    clearId('status');
    clearId('title');

    // Get the titl things out of the globar card index object
    var titl = cardex.getElementsByTagName('titl');

    // If there's nothing here, try getting it again
    if (titl.length == 0) {
	if (selCards.loop < 5) { selCardsReset() }
	else { innerPs('Sorry, there was a problem...') }
	selCards.loop++;  // Don't want any evil looping...
	return;
    }
    selCards.loop = 0;

    // Make sure the "Get Cards" option is available
    tb.g.on = "yes";
    showToolbar();

    // Make the most recent cards the default
    if ( x == undefined || x == null) {
	x = Math.floor(titl.length/LIST);
	if ( x > 0) { if (titl.length/x == LIST) { x = x-1 } }
    }
    // Maybe build some sanity checking for x here???
    // Build the selection toolbar
    for (i = 0; i < (titl.length/LIST); i++) { 
	lb = i*LIST+1;
	ub = i*LIST+LIST;
	if (i == x) { selbar = selbar+'<b>'; }
	selbar = selbar+'<a href="javascript:selCards('+i+')">['+lb+'-'+ub+']</a>';
	if (i == x) { selbar = selbar+'</b>'; }
    }
    selbar = selbar + ' -- <a href="javascript:selCardsReset()>Reset</a>';
    // Clear out the cardbox and display the stuff
    clearIt('cbox');
    innerPs(selbar,'toolbar');
    lb = x*LIST;
    ub = x*LIST+LIST;
    if (ub > titl.length) { ub = titl.length }
    innerPs("&nbsp;",'ljmini');
    for (i = lb; i < ub; i++) {
	var color = "csoff";
	if (cardsel[i] == "yes") { color = "cson" }
	rtext = getText(titl[i]);
	stext = '<a class="'+color+'" href="javascript:selCardsSel('+i+')">'+rtext+'</a>';
	innerPs(stext,'ljmini','sbid'+i);
    }
}    

// Select a card-set, x is the number of the card-set selected
function selCardsSel(x) {
    var mine = document.getElementById('sbid'+x);
    var itex = getText(mine);

    if (cardsel[x] == "yes") {
	itex = '<a class="csoff" href="javascript:selCardsSel('+x+')">'+itex+'</a>';
	cardsel[x] = "no";
	csseld--;
    }
    else {
	itex = '<a class="cson" href="javascript:selCardsSel('+x+')">'+itex+'</a>';
	cardsel[x] = "yes";
	csseld++;
    }
    statMess(csseld+' selected');
    mine.innerHTML = itex;
}

// Reset the selection and reload the index
function selCardsReset() {
    var name = [];
    var tag = [];
    var url = BACKEND+'?iList';

    // Adjust the menu options
    menuToolbar();

    clearIt("cbox");
    innerPs("Please wait...");

    // Clear the card index object and array
    cardex = {};  // Should I redefine this as an XML doc?
    cardsel = [];

    // Get the index data
    sreq = new XMLHttpRequest();
    sreq.onreadystatechange = gotSome;
    sreq.open("GET", url, true);
    sreq.send(null);
}
 
// Shuffle the given array
// http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
function shuffleCards(array) {
    var temp;
    var k;

    var n = array.length;
    while (n > 1) {
	k = Math.floor(Math.random()*n);  // 0 <= k < n
	n--;
	temp = array[n];
	array[n] = array[k];
	array[k] = temp;
    }
}

function reShuffle() {

    shuffleCards(cardRef);
    statMess('Cards Reshuffled');

}

// Request a set of cards based on cardex
function getCards() {
    var cstags = '';
    var csname = '';
    var url = BACKEND+'?';
    var i = 0;

    // Get the card set names out of the global card index object
    var cnam = cardex.getElementsByTagName('name');

    // Create the url arguments
    for (i = 0; i < cardsel.length; i++) {
	if ( cardsel[i] == 'yes') {
	    csname = getText(cnam[i]);
	    cstags = cstags + 'cset='+csname+'&' }
    }

    // Send the request
    if ( cstags.length > 1 ) {
	clearIt('cbox');
	innerPs('Please wait');

	url = url+cstags;
	sreq = new XMLHttpRequest();
	sreq.onreadystatechange = gotSome;
	sreq.open("GET", url, true);
	sreq.send(null);
	
	// Reset the card counter
	cardCurrent = -1;
    }
    else { statMess("No card sets are selected") }
}

function spewCards() {
    for (var i = 0; i < cardRef.length; i++) {
	innerPs('Spew: '+cardRef[i],'ljmini');
    }
}

// Deal with returned card data
// (cdom is a DOM object with the returned card names)
function getCards2(cdom) {
    var i = 0;
    var cardname = '';

    // This becomes an array-like object of DOM nodes with the tag 'card'
    var cnamexml = cdom.getElementsByTagName('card');

    if (cnamexml.length > 0) {
	card = {}; // Reset the global card object
	cardRef = []; // Reset the card-ref array
	for (i = 0; i < cnamexml.length; i++) {
	    cardname = getText(cnamexml[i]); // Pull the text from DOM nodes
	    card[cardname] = { }; // Create an object within this object
	    //card[i].name = getText(cnamexml[i]); // Pull the text from DOM nodes
	    cardRef[i]=cardname;
	}
	card.length = i;  // This is not a universal object property
	shuffleCards(cardRef);
	//spewCards();
	cardToolbar();
	nextCard();
    }
    else {
	innerPs('There was a problem, please try selecting your card sets again.');
    }
}

function cardDirection() {
    inkDec("dec");
    cardDirRandom++;
    if (cardDirRandom == 1) { statMess('Random') }
    else if (cardDirRandom == 2) { statMess('Reverse') }
    else {
	cardDirRandom = 0;
	statMess('Forward');
    }
    nextCard();
}

//Show the next pard of the card
function flipCard() {
    
    var max = cardRef.length;
    statMess(cardCurrent+1+" of "+max+" | "+card[cardRef[cardCurrent]].title,'title');
    if (card[cardRef[cardCurrent]].q[cardPart] != undefined) {
	innerPs(card[cardRef[cardCurrent]].q[cardPart]);
	cardPart = cardPart+cardDir;
    }
    else { nextCard() }
}

//Remove a card from the active deck
function removeCard() {
    if (cardRef.length == 1) { statMess('There is only one card left') }
    else { 
	statMess('Card '+cardRef[cardCurrent]+' removed');
	cardRef.splice(cardCurrent,1);
	inkDec('dec');
	nextCard();
    }
}

//Go back to the cards from the select screen, without doing the selection.
function goBack() {
    inkDec('dec');
    cardToolbar()
    nextCard();
}

//Learn the card
function learnCard() {
    if (cardDir == 1 && cardPart == 1) {
	statMess('View card first');
	return;
    }
    var silly = card[cardRef[cardCurrent]].q.length-2;
    if (cardDir == -1 && cardPart == silly) {
	statMess('View card first');
	return;
    }

    var uninc = 'yes';
    var adv = Math.floor(Math.random()*4)+4; // Random number between 4 and 7    
    var insert = cardCurrent + adv;
    var pull = cardRef.splice(cardCurrent,1);
    if (insert >= cardRef.length) { 
	insert = insert - cardRef.length;
	uninc = 'no';
    }
    cardRef.splice(insert,0,pull);
    var iplus = insert+1;
    statMess('Pull: '+pull+' to: '+iplus);
    if (uninc == 'yes') { inkDec('dec') }
    nextCard();
}

//Get the data for the next 10 cards
function fillNextTen() {
    var i = 0;
    var ctg = 20; // Yeah, I started with 10...
    var url = BACKEND;
    var urlargs = '?';

    // Don't try to get more cards then there are
    var max = cardRef.length;
    var top = cardCurrent + ctg;
    if ( top > max ) { top = max }

    // innerPs('cur: '+cardCurrent+' max: '+max+' top:'+top);

    for (i=cardCurrent; i < top; i++) {
	if (card[cardRef[i]].title == undefined) {
		urlargs = urlargs+'cDat='+cardRef[i]+'&';
	}
    }
    
    // innerPs('args: '+urlargs,"ljmini");

    if (urlargs.length > 1) {
	url=url+urlargs;
	sreq = new XMLHttpRequest;
	sreq.onreadystatechange = gotSome;
	sreq.open("GET", url, true);
	sreq.send(null);
    }
    else { statMess("Weird, there are no cards in the selected sets..."); }
}

// Account for the card counter wraping around
function inkDec(id) {
    if (id == "inc") { cardCurrent++; }
    else { cardCurrent--; }
    if (cardCurrent >= cardRef.length) { cardCurrent = 0 }
    if (cardCurrent < 0) { cardCurrent = cardRef.length - 1 }
}

// Show the card before this card
function lastCard() {
    inkDec('dec');
    inkDec('dec');
    nextCard();
}

// Display the questions from the next card
function nextCard() {
    var counter = 0;
    inkDec('inc');
    var kpf = cardCurrent+5;

    // If there's no data, get some
    if (card[cardRef[cardCurrent]].title == undefined) { 
	cardCN = "yes"; // Call this function again when we get data
	fillNextTen();
	return;
    }
    if (kpf < cardRef.length && card[cardRef[kpf]].title == undefined) { fillNextTen() }

    // Set the card direction
    if (cardDirRandom == 0) { cardDir = 1 }
    else if (cardDirRandom == 1) { 
	if (Math.floor(Math.random()*2) > 0) { cardDir = 1 }
	else { cardDir = -1 }
    }
    else { cardDir = -1 }
    if (cardDir == 1) { cardPart = 0 }
    else { cardPart = card[cardRef[cardCurrent]].q.length-1 }

    if (clearStat != "no") { clearId('status') }
    else { clearStat = "yes" }

    clearIt('cbox');
    innerPs('&nbsp;');
    flipCard();
}

function cardUp(cdom) { // cdom = dom object with the card data
    var i = 0;
    var j = 0;
    var holder = '';
    var cardname = '';

    for (i = 0; i < cdom.length; i++) {
 	var ink = cdom[i].getElementsByTagName('name');
	cardname = getText(ink[0]);
	var ink = cdom[i].getElementsByTagName('title');
	holder = getText(ink[0]);
	card[cardname].title = holder;
	// innerPs('Title '+i+': '+holder,'ljmini');
	var quest = cdom[i].getElementsByTagName('q');
	if (quest.length > 0) {
	    // Initialize the relivant part of the crazy card object if necessary.
	    if (card[cardname].q == undefined) { card[cardname].q = [] }
	    for (j = 0; j < quest.length; j++) {
		holder = getText(quest[j]);
		card[cardname].q[j] = holder;
		//innerPs('Question '+i+'-'+j+': '+holder,'ljmini');
	    }
	}
    }
    
    // nextCard was unable to display any cards.
    if (cardCN == "yes") { 
	cardCN = "no";
	inkDec("dec");
	nextCard();
    }
}

// Deal with incoming data from XMLHttpRequest events
function gotSome() {

    if (sreq.readyState != 4) { 
	statMess("Waiting");
	return;
    }
    if (sreq.status != 200) {
	clearIt("cbox");
	innerPs("Could not contact server");
	statMess("Server doesn't like me");
    }
    else { 
	//statMess("Yay!  "+sreq.status);
	statMess("Dat rec");
	var lump = sreq.responseXML; // Got a big xml object
	// Now try to get some text out of it...
	// Note than getElementsByTagName() returns an array-like object
	if (lump.getElementsByTagName('indexlist')[0] != undefined) {
	    var il = lump.getElementsByTagName('indexlist');
	    // il[0] is the xml object <indexlist> ... </indexlist>
	    cardex = il[0];  // Set the global card index XML object
	    selCards();
	}
	if (lump.getElementsByTagName('cardlist')[0] != undefined) {
	    var il = lump.getElementsByTagName('cardlist');
	    getCards2(il[0]);
	}
	if (lump.getElementsByTagName('carddata')[0] != undefined) {
	    var il = lump.getElementsByTagName('carddata');
	    cardUp(il);
	}
    }
}

// This function is called onkeyup from <body>
function keyEvent(event) {
    var e = event || window.event; // For IE event model
    var code = e.keyCode;
    var kname = keyMap[code];

    if (tb[kname] == undefined) { return }
    // I don't know why I can't put this ^ below that v....
    if (tb[kname].alias != undefined) { kname = tb[kname].alias }
    tb[kname].launch();
}

// Welcome message
function welcomeMess() {
    // Clear the cardbox
    clearIt("cbox");

    // Display the welcome message
    var style = "ljmini";
    innerPs("&nbsp;",style);
    innerPs("&nbsp;",style);
    innerPs("Welcome to the experimental flashcard app.",style);
    innerPs("&nbsp;",style);
    innerPs('Go ahead; click on "Select" to select some cards ^^',style);
    innerPs("&nbsp;",style);
    innerPs("Note:  This is a single page javascript application.",style);
    innerPs('So far it has only been tested on Mozilla/Firefox type',style);
    innerPs("Browsers.  It probably won't work on IE because I haven't",style);
    innerPs("added any of the silly, non-standard MS code.",style);
}

// Called when the document is loaded
function docLoad() {
    // Set-up the header
    clearIt('header');
    inHeader('愚かな　フラッシュカード','line');
    inHeader('','toolbar','toolbar');

    // Interval timer for the clock (after the document has loaded)
    statClock();
    setInterval('statClock()',60000);
    // Check for a cookie to see if we're logged in

    // Display the toolbar
    showToolbar();

    // Show the welcome message
    welcomeMess();
}

