/* #####################################################################
 *     Javascript Toolbox
 *     Copyright 2010 Luca Ghezzi
 *     www.geniopensante.it
 * ##################################################################### */

// general purpose variables
var ActiveTool = "Clock";
var Timer;

// variables for the Calendar tool
var CalendarMonth;
var CalendarYear;
var CalendarFirst=1;
var CalendarHist=0;

// variables for the Week tool
var WeekFirstDay;
var WeekMonth;
var WeekYear;
var WeekFirst=1;
var WeekHist=0;

// variables for the calculator tool
var CalcStack = new Array();
var CalcStackEmpty=1;
var CalcMem = new Array();
var CalcDecimal=0;
var CalcTrailing0s=0;
var CalcTolerance=1.e-13;
var CalcModeStack=0;
var CalcModeAngle=0;
var CalcModeKeep=0;
var CalcModeComm=0;
var CalcModeRename=0;

/* #####################################################################
 * Main tool commander
 * ##################################################################### */

function showTool()
{
  if (ActiveTool == "Clock")     { showClock();      Timer=setTimeout("showTool()", 1000); }
  if (ActiveTool == "Calendar")  { showCalendar();   Timer=setTimeout("showTool()",60000); }
  if (ActiveTool == "Week")      { showWeek();       Timer=setTimeout("showTool()",60000); }
  if (ActiveTool == "Calculator"){ showCalculator();                                       }
  if (ActiveTool == "Themes")    { showThemes();                                           }
  return null;
}

/* #####################################################################
 * Clock tool
 * ##################################################################### */

function showClock()
{
  // local variables
  var txt="";
  var apx='"';
  var dayname, monthname;
  var now=new Date();
  var day=now.getDay();
  var hrs=now.getHours();
  var min=now.getMinutes();
  var sec=now.getSeconds();
  var yyyy=now.getFullYear();
  var mm=now.getMonth()+1;
  var dd=now.getDate();

  // day of the week
  if (day==0) dayname="Sunday";
  if (day==1) dayname="Monday";
  if (day==2) dayname="Tuesday";
  if (day==3) dayname="Wednsday";
  if (day==4) dayname="Thursday";
  if (day==5) dayname="Friday";
  if (day==6) dayname="Saturday";
  
  // month: first 3 letters of the english name
  if (mm==1)  monthname="JAN";
  if (mm==2)  monthname="FEB";
  if (mm==3)  monthname="MAR";
  if (mm==4)  monthname="APR";
  if (mm==5)  monthname="MAY";
  if (mm==6)  monthname="JUN";
  if (mm==7)  monthname="JUL";
  if (mm==8)  monthname="AUG";
  if (mm==9)  monthname="SEP";
  if (mm==10) monthname="OCT";
  if (mm==11) monthname="NOV";
  if (mm==12) monthname="DEC";

  // leading zero before short minutes and seconds 
  if (hrs<=9) hrs="0"+hrs;
  if (min<=9) min="0"+min;
  if (sec<=9) sec="0"+sec;

  txt=txt + "<center>";

  txt=txt + dayname+", "+dd+" "+monthname+" "+yyyy+"<br>";
  txt=txt + "<div id="+apx+"clock"+apx+"><b>"+hrs+" : "+min+" : "+sec+"</b></div>";
  
  // html line to select a different tool
  txt=txt + toolSelector("clearTimeout(Timer); ActiveTool='Themes'; showTool();","Clock","clearTimeout(Timer); ActiveTool='Calendar'; CalendarFirst=1; showTool();");
  
  txt=txt + "</center>";

  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;
}

/* #####################################################################
 * Calendar tool
 * ##################################################################### */

function showCalendar()
{
  var txt="";
  
  if (CalendarFirst == 1) 
  {
  	var now = new Date();
    CalendarMonth=now.getMonth()+1; 
    CalendarYear=now.getFullYear(); 
  }
  txt += displayMonth(CalendarMonth,CalendarYear);

  // html line to select a different tool
  txt += toolSelector("clearTimeout(Timer); ActiveTool='Clock'; showTool();","Calendar","clearTimeout(Timer); ActiveTool='Week'; WeekFirst=1; showWeek();") + "<br>";

  txt += "</center>";
  
  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;
}

/* #####################################################################
 * english full name of a specified month (number 1-12)
 * ##################################################################### */

function monthFullName(mm)
{
  var name;
  
  switch (mm)
  {
    case 1:  name="January";   break;
    case 2:  name="February";  break; 
    case 3:  name="March";     break;
    case 4:  name="April";     break; 
    case 5:  name="May";       break;    
    case 6:  name="June";      break; 
    case 7:  name="July";      break; 
    case 8:  name="August";    break;  
    case 9:  name="September"; break;
    case 10: name="October";   break; 
    case 11: name="November";  break;
    case 12: name="December";  break;
  }
  
  return name;
}

/* #####################################################################
 * english short name of a specified month (number 1-12)
 * ##################################################################### */

function monthShortName(mm)
{
  var name;
  
  switch (mm)
  {
    case 1:  name="Jan"; break;
    case 2:  name="Feb"; break; 
    case 3:  name="Mar"; break;
    case 4:  name="Apr"; break; 
    case 5:  name="May"; break;    
    case 6:  name="Jun"; break; 
    case 7:  name="Jul"; break; 
    case 8:  name="Aug"; break;  
    case 9:  name="Sep"; break;
    case 10: name="Oct"; break; 
    case 11: name="Nov"; break;
    case 12: name="Dec"; break;
  }
  
  return name;
}

/* #####################################################################
 * number of the day within the week, from monday (1) to sunday (7)
 * ##################################################################### */

function dayNumber(n)
{
  if (n==0) { return 7; }
  else      { return n; }
}

/* #####################################################################
 * number of days in a specified month (number 1-12)
 * the year is required to handle the leap year exception
 * ##################################################################### */

function monthNumDays(mm,yyyy)
{
  var numdays;

  switch (mm)
  {
    case 1:  numdays=31; break;
    case 2:  numdays=28; break; 
    case 3:  numdays=31; break;
    case 4:  numdays=30; break; 
    case 5:  numdays=31; break;    
    case 6:  numdays=30; break; 
    case 7:  numdays=31; break; 
    case 8:  numdays=31; break;  
    case 9:  numdays=30; break;
    case 10: numdays=31; break; 
    case 11: numdays=30; break;
    case 12: numdays=31; break;
  }

  // leap year
  if (mm==2)
  {
    if (yyyy %   4 == 0) numdays=29;
    if (yyyy % 100 == 0) numdays=28;
    if (yyyy % 400 == 0) numdays=29;
  }
  
  return numdays;
}

/* #####################################################################
 * switch to the next month in the calendar tool
 * ##################################################################### */

function incrCalendarMonth()
{
  // history of increments and decrements
//  CalendarHist++;
  
  // increment month number, accounting for modular arithmetic
  CalendarMonth++; if (CalendarMonth > 12) { CalendarMonth=1; CalendarYear++; }
  
  // leave virgin state
  CalendarFirst=0;
  
  // possibly restore the virgin state
//  if (CalendarHist == 0) CalendarFirst=1; 

  return null;
}

/* #####################################################################
 * switch to the previous month in the calendar tool
 * ##################################################################### */

function decrCalendarMonth()
{
  // history of increments and decrements
//  CalendarHist--;

  // decrement month number, accounting for modular arithmetic
  CalendarMonth--; if (CalendarMonth < 1) { CalendarMonth=12; CalendarYear--; }
  
  // leave virgin state
  CalendarFirst=0;
  
  // possibly restore the virgin state
//  if (CalendarHist == 0) CalendarFirst=1; 

  return null;
}

/* #####################################################################
 * display the array of the days of the month in the calendar tool
 * ##################################################################### */

function displayMonth(mm,yyyy)
{
  // local variables
  var txt="";
  var apx='"';
  var i,j,k,fdn,nw,ldn;

  // month full name
  monthname=monthFullName(mm);
  
  // first day of the month
  var firstday = new Date(yyyy,mm-1,1);
  var fdn = dayNumber(firstday.getDay());
  
  // vector with days of the month, leading & trailing blanks
  var days = new Array();
  for (i=1; i<fdn; i++)                    { days.push(""); }  // leading blanks
  for (i=1; i<=monthNumDays(mm,yyyy); i++) { days.push(i);  }  // days of the month
  ldn = days.length%7;
  if (ldn>0) { for (i=1; i<=7-ldn; i++)    { days.push(""); }} // trailing blanks	
  
  // today?
  var today = new Date();
  var d = today.getDate();
  var m = today.getMonth()+1;
  var y = today.getFullYear();
  for (i=0; i<days.length; i++)
  {
  	if (d==days[i] && m==mm && y==yyyy) 
  	{ 
  	  days[i] = "<a onclick=" + apx + "ActiveTool='Clock'; showClock();" + apx + " href='#'><b>" + days[i] + "</b></a>";
  	}
  }
  
  // number of weeks
  nw=days.length/7;

  // incipit
  txt += "<br>";
  txt += "<center>";
  
  // month and year
  txt += "<a onclick='decrCalendarMonth(); showCalendar();' href='#'><b>&lt;</b>&nbsp;</a>"
  txt += "<a onclick='monthSelector();'><b>" + monthname + "</b></a> <a onclick='yearSelector();'><b>" + yyyy + "</b></a>"
  txt += "<a onclick='incrCalendarMonth(); showCalendar();' href='#'>&nbsp;<b>&gt;</b></a>&nbsp;<br>"

  txt += "<table>";
  
  // key with day names
  txt += "<tr height='24'>";
  txt += "<td align='center' width='45'><i>Mon</i></td>";
  txt += "<td align='center' width='45'><i>Tue</i></td>";
  txt += "<td align='center' width='45'><i>Wed</i></td>";
  txt += "<td align='center' width='45'><i>Thu</i></td>";
  txt += "<td align='center' width='45'><i>Fri</i></td>";
  txt += "<td align='center' width='45'><i>Sat</i></td>";
  txt += "<td align='center' width='45'><i>Sun</i></td>";
  txt += "<td align='center' width='100'></td>";
  txt += "</tr>";
  
  // week-by-week
  k=0;
  for (i=1; i<=nw; i++)
  {
    txt += "<tr height='22'>";
    for (j=1; j<=7; j++) { txt += "<td align='center'>" + days[k] + "</td>"; k++; }
    txt += "</tr>";
  }

  txt += "</table>";

  return txt;
}

/* #####################################################################
 * month selector
 * ##################################################################### */

function monthSelector()
{
  var txt="";
  
  txt += "<br>";
  txt += "<center>";
  txt += "<table>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarMonth=1;  CalendarFirst=0; showCalendar();'><b>JAN</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=2;  CalendarFirst=0; showCalendar();'><b>FEB</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=3;  CalendarFirst=0; showCalendar();'><b>MAR</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=4;  CalendarFirst=0; showCalendar();'><b>APR</b></a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarMonth=5;  CalendarFirst=0; showCalendar();'><b>MAY</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=6;  CalendarFirst=0; showCalendar();'><b>JUN</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=7;  CalendarFirst=0; showCalendar();'><b>JUL</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=8;  CalendarFirst=0; showCalendar();'><b>AUG</b></a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarMonth=9;  CalendarFirst=0; showCalendar();'><b>SEP</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=10; CalendarFirst=0; showCalendar();'><b>OCT</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=11; CalendarFirst=0; showCalendar();'><b>NOV</b></a></td>";
  txt += "<td width='50'><a onclick='CalendarMonth=12; CalendarFirst=0; showCalendar();'><b>DEC</b></a></td>";
  txt += "</tr>";
  txt += "</table>";

  // html line to select a different tool
  txt += toolSelector("clearTimeout(Timer); ActiveTool='Clock'; showTool();","Calendar","clearTimeout(Timer); ActiveTool='Week'; WeekFirst=1; showWeek();") + "<br>";

  txt += "</center>";
  
  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;	
}

/* #####################################################################
 * year selector
 * ##################################################################### */

function yearSelector()
{
  var txt="";

  txt += "<br>";
  txt += "<center>";
  txt += "<table>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear-12) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear-12) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear-11) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear-11) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear-10) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear-10) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 9) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 9) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 8) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 8) + "</a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 7) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 7) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 6) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 6) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 5) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 5) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 4) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 4) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 3) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 3) + "</a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 2) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 2) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear- 1) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear- 1) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + CalendarYear      + ";  CalendarFirst=0; showCalendar();'>" + CalendarYear      + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 1) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 1) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 2) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 2) + "</a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 3) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 3) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 4) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 4) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 5) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 5) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 6) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 6) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 7) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 7) + "</a></td>";
  txt += "</tr>";
  txt += "<tr>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 8) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 8) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+ 9) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+ 9) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+10) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+10) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+11) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+11) + "</a></td>";
  txt += "<td width='50'><a onclick='CalendarYear=" + (CalendarYear+12) + ";  CalendarFirst=0; showCalendar();'>" + (CalendarYear+12) + "</a></td>";
  txt += "</tr>";
  txt += "</table>";

  // html line to select a different tool
  txt += toolSelector("clearTimeout(Timer); ActiveTool='Clock'; showTool();","Calendar","clearTimeout(Timer); ActiveTool='Week'; WeekFirst=1; showWeek();") + "<br>";

  txt += "</center>";
  
  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;	
}

/* #####################################################################
 * Week tool
 * ##################################################################### */

function showWeek()
{
  // local variables
  var txt="";
  var apx='"';
  var now=new Date();
  var yyyy=now.getFullYear();
  var mm=now.getMonth()+1;
  var day=now.getDay();
  var dd=now.getDate();
  var firstday;
  var d=0;
  var w=0;
  
  // first day of the current week
  if (day==0) day=7;
  firstday=dd-day+1;

  if (WeekFirst == 1) 
  { 
    WeekMonth=mm; 
    WeekYear=yyyy; 
    WeekFirstDay=firstday;
    txt += displayWeek(WeekFirstDay,dd,WeekMonth,WeekYear);
  }
  else
  {
  	txt += displayWeek(WeekFirstDay,WeekFirstDay,WeekMonth,WeekYear);
  }
  
  // html line to select a different tool
  txt += toolSelector("clearTimeout(Timer); ActiveTool='Calendar'; CalendarFirst=1; showTool();","Week","clearTimeout(Timer); ActiveTool='Calculator'; showCalculator();") + "<br>";

  txt += "</center>";
  
  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;
}

/* #####################################################################
 * computes the number of the day within the year
 * ##################################################################### */

function getDayNumber(dd,mm,yyyy) 
{
  // local variables
  var i, d=1;
  
  // positioning onto the correct month
  for (i=1; i<mm; i++) d+=monthNumDays(i,yyyy); 
  
  // positioning onto the correct day
  for (i=1; i<dd; i++) d++;                     
  
  // number of the day within the year
  return d;
}

/* #####################################################################
 * computes the number of the week within the year
 * ##################################################################### */

function getWeekNumber(dd,mm,yyyy)
{
  // local variables
  var d, w, Jan1, d1, mon;

  // info about the new year's day
  Jan1 = new Date (yyyy,0,1);  
  d1=Jan1.getDay();

  // number of the day within the year
  d=getDayNumber(dd,mm,yyyy)
  
  // initialization
  if (d1 == 1) { w=1; mon=1;      }
  else         { w=0; mon=7-d1+2; }

  // positioning onto the correct week
  while (d >= mon+7) { w++; mon+=7; } w++;

  // correct if it is the last week of the year ending into the new year
  if (w==1 & d<mon) w=52;
  
  // number of the week within the year
  return w
}

/* #####################################################################
 * switch to the next week in the week tool
 * ##################################################################### */

function incrWeek()
{
  // local variables
  var FictDate; 
  
  // history of increments and decrements
  WeekHist++;
  
  // first day of the new week
  WeekFirstDay += 7;
  if (WeekFirstDay > monthNumDays(WeekMonth,WeekYear))
  {
    // correct when the first day falls into the next month
    WeekFirstDay += -monthNumDays(WeekMonth,WeekYear); 

    // increment month number, accounting for modular arithmetic
  	WeekMonth++; if (WeekMonth > 12) { WeekMonth=1; WeekYear++; }
  }
  
  // leave virgin state
  WeekFirst=0;
  
  // possibly restore the virgin state
  if (WeekHist == 0) WeekFirst=1; 

  return null;
}

/* #####################################################################
 * switch to the previous week in the week tool
 * ##################################################################### */

function decrWeek()
{
  // local variables
  var FictDate; 
  
  // history of increments and decrements
  WeekHist--;
  
  // first day of the new week
  WeekFirstDay += -7;
  if (WeekFirstDay <= 0)
  {
    // decrement month number, accounting for modular arithmetic
  	WeekMonth--; if (WeekMonth < 1) { WeekMonth=12; WeekYear--; }

  	// correct when the first day falls into the previous month
    WeekFirstDay += monthNumDays(WeekMonth,WeekYear); 
  }
  
  // leave virgin state
  WeekFirst=0;
  
  // possibly restore the virgin state
  if (WeekHist == 0) WeekFirst=1; 

  return null;
}

/* #####################################################################
 * display the array of the days of the week in the week tool
 * ##################################################################### */

function displayWeek(firstday,dd,mm,yyyy)
{
  // local variables
  var txt="";
  var apx='"';
  var w, l, i, d;
  
  txt += "<br>";
  txt += "<center>";

  // week number
  w=getWeekNumber(dd,mm,yyyy);
  txt += "<a onclick='decrWeek(); showWeek();' href='#'><b>&lt;</b>&nbsp;</a>"
  txt += "<b>Week " + w + "</b>"
  txt += "<a onclick='incrWeek(); showWeek();' href='#'>&nbsp;<b>&gt;</b></a>&nbsp;<br>"

  txt += "<table>";
  
  // key with day names
  txt += "<tr height='24'>";
  txt += "<td align='center' width='45'><i>Mon</i></td>";
  txt += "<td align='center' width='45'><i>Tue</i></td>";
  txt += "<td align='center' width='45'><i>Wed</i></td>";
  txt += "<td align='center' width='45'><i>Thu</i></td>";
  txt += "<td align='center' width='45'><i>Fri</i></td>";
  txt += "<td align='center' width='45'><i>Sat</i></td>";
  txt += "<td align='center' width='45'><i>Sun</i></td>";
  txt += "<td align='center' width='100'></td>";
  txt += "</tr>";

  // day numbers
  txt += "<tr height='22'>";
  for (i=0; i<7; i++)
  {
  	// initialization
  	d=firstday+i;
  	
  	// correct if the week ends into the next month
  	if (d > monthNumDays(mm,yyyy)) d += -monthNumDays(mm,yyyy);
  	
  	// correct if the week starts from the previous month
  	if (d <= 0) 
  	{
  	  if (mm > 1) d += monthNumDays(mm-1,yyyy);
  	  else        d += monthNumDays(12-1,yyyy-1);
  	}

  	// write
  	if (d==dd & WeekFirst==1)
  	{
  	  txt += "<td align='center'><a onclick=" + apx + "ActiveTool='Clock'; showClock();" + apx + " href=''><b>" + d + "</b></a></td>";	
  	}
  	else
  	{
  	  txt += "<td align='center'>" + d + "</td>";	
  	}
  }
  txt += "</tr>";

  // month
  txt += "<tr>";
  for (i=0; i<7; i++)
  {
  	// initialization
  	d=firstday+i; m=mm;

  	// correct if the week ends into the next month
  	if (d > monthNumDays(mm,yyyy)) 
  	{
  	  if (mm < 12) m++;
  	  else         m=1;
  	}

  	// correct if the week starts from the previous month
  	if (d <= 0) 
  	{
  	  if (mm > 1) m--;
  	  else        m=12;
  	}

  	// write
  	txt += "<td align='center'><i>" + monthShortName(m) + "</i></td>";	
  }
  txt += "</tr>";
  
  txt += "</table>";
  
  // weeks left
  l=52-w;
  if (l == 1) txt += "<br>" + l + " week left in Y" + yyyy + "<br>";
  else        txt += "<br>" + l + " weeks left in Y" + yyyy + "<br>";

  return txt;
}

/* #####################################################################
 * Calculator tool
 * ##################################################################### */

function cplx(x,y)
{
  this.Re = x;
  this.Im = y;
}

function mem(Name,Value)
{
  this.Name  = Name;
  this.Value = Value;
}

function commandRow (Commands, Names)
{
  this.Commands = Commands;
  this.Names    = Names;
}

function showCalculator()
{
  // local variables
  var txt1="";
  var txt2="";
  var apx='"';
  var i;
  var CommandRowSx = new Array();
  var CommandRowDx = new Array();
  var ValModeStack = new Array();
  var ValModeAngle = new Array();
  var ValModeKeep  = new Array();
  
  // ways to manipulate the stack
  ValModeStack[0]="PICK";
  ValModeStack[1]="ROLL UP";
  ValModeStack[2]="ROLL DW";
  ValModeStack[3]="SWAP";
  ValModeStack[4]="DUPL";
  ValModeStack[5]="DROP";
  
  // ways to express angles
  ValModeAngle[0]="RAD";
  ValModeAngle[1]="DEG";
  ValModeAngle[2]="GRAD";
  
  // ways to keep operands when they are operated
  ValModeKeep[0]="LOOSE";
  ValModeKeep[1]="KEEP";
  
  // button row #0
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcExp();";          Names[0] = "EXP";
  Commands[1] = "calcPow10();";        Names[1] = "10<sup>x</sup>";
  Commands[2] = "calcLn();";           Names[2] = "LN";
  Commands[3] = "calcLog();";          Names[3] = "LOG";
  Commands[4] = "calcLogb();";         Names[4] = "LOG<sub>b</sub>";
  Commands[5] = "calcPi();";           Names[5] = "PI";
  Commands[6] = "calcSquare();";       Names[6] = "x<sup>2</sup>";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[0] = Row;
  
  // button row #1
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcSin();";          Names[0] = "SIN";
  Commands[1] = "calcCos();";          Names[1] = "COS";
  Commands[2] = "calcTan()";           Names[2] = "TAN";
  Commands[3] = "calcCsc();";          Names[3] = "CSC";
  Commands[4] = "calcSec();";          Names[4] = "SEC";
  Commands[5] = "calcCot();";          Names[5] = "COT";
  Commands[6] = "calcCube();";         Names[6] = "x<sup>3</sup>";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[1] = Row;

  // button row #2
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcAsin();";         Names[0] = "ASIN";
  Commands[1] = "calcAcos();";         Names[1] = "ACOS";
  Commands[2] = "calcAtan()";          Names[2] = "ATAN";
  Commands[3] = "calcAcsc();";         Names[3] = "ACSC";
  Commands[4] = "calcAsec();";         Names[4] = "ASEC";
  Commands[5] = "calcAcot();";         Names[5] = "ACOT";
  Commands[6] = "calcPow();";          Names[6] = "y<sup>x</sup>";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[2] = Row;

  // button row #3
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcSinh();";         Names[0] = "SINH";
  Commands[1] = "calcCosh();";         Names[1] = "COSH";
  Commands[2] = "calcTanh()";          Names[2] = "TANH";
  Commands[3] = "calcCsch();";         Names[3] = "CSCH";
  Commands[4] = "calcSech();";         Names[4] = "SECH";
  Commands[5] = "calcCoth();";         Names[5] = "COTH";
  Commands[6] = "calcSqrt();";         Names[6] = "SQRT";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[3] = Row;

  // button row #4
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcAsinh();";        Names[0] = "ASINH";
  Commands[1] = "calcAcosh();";        Names[1] = "ACOSH";
  Commands[2] = "calcAtanh()";         Names[2] = "ATANH";
  Commands[3] = "calcAcsch();";        Names[3] = "ACSCH";
  Commands[4] = "calcAsech();";        Names[4] = "ASECH";
  Commands[5] = "calcAcoth();";        Names[5] = "ACOTH";
  Commands[6] = "calcCbrt();";         Names[6] = "CBRT";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[4] = Row;
  
  // button row #5
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calci();";            Names[0] = "i";
  Commands[1] = "calcRe();";           Names[1] = "RE";
  Commands[2] = "calcIm();";           Names[2] = "IM";
  Commands[3] = "calcAbs()";           Names[3] = "| x |";
  Commands[4] = "calcArg();";          Names[4] = "ARG";
  Commands[5] = "calcConj();";         Names[5] = "CONJ";
  Commands[6] = "calcRoot();";         Names[6] = "y<sup>1/x</sup>";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[5] = Row;

  // button row #6
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcXY2cplx();";      Names[0] = "x+iy";
  Commands[1] = "calcRhoArg2cplx();";  Names[1] = "xe<sup>iy</sup>";
  Commands[2] = "calcUroot();";        Names[2] = "1<sup>1/n<\sup>";
  Commands[3] = "calcIntDiv();";       Names[3] = "INT&divide";
  Commands[4] = "calcMod();";          Names[4] = "MOD";
  Commands[5] = "calcGCD();";          Names[5] = "GCD";
  Commands[6] = "calcLCM();";          Names[6] = "lcm";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[6] = Row;

  // button row #7
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcIp();";           Names[0] = "IP";
  Commands[1] = "calcFp();";           Names[1] = "FP";
  Commands[2] = "calcRnd();";          Names[2] = "RND";
  Commands[3] = "calcTrunc();";        Names[3] = "TRUNC";
  Commands[4] = "calcFloor();";        Names[4] = "FLOOR";
  Commands[5] = "calcCeil();";         Names[5] = "CEIL";
  Commands[6] = "calcSign();";         Names[6] = "SGN";
  
  var Row = new commandRow(Commands,Names);
  CommandRowSx[7] = Row;

  // button row #8
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcEulerGamma()";    Names[0] = "Gamma";
  Commands[1] = "calcLnGamma()";       Names[1] = "Gammaln";
  Commands[2] = "calcFact();";         Names[2] = "!";
  Commands[3] = "calcBinomial();";     Names[3] = "BINOM";
  Commands[4] = "calcBeta();";         Names[4] = "Beta";
  Commands[5] = "calcLnBeta();";       Names[5] = "Betaln";
  Commands[6] = "calcRandom();";       Names[6] = "RAND";
 
  var Row = new commandRow(Commands,Names);
  CommandRowSx[8] = Row;

  // button row #9
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "calcIncr()";          Names[0] = "++";
  Commands[1] = "calcDecr()";          Names[1] = "- -";
  Commands[2] = "calcEqn2()";          Names[2] = "EQN2";
  Commands[3] = "calcEqn3()";          Names[3] = "EQN3";
  Commands[4] = "calcEqn4()";          Names[4] = "EQN4";
  Commands[5] = "calcMin();";          Names[5] = "MIN";
  Commands[6] = "calcMax();";          Names[6] = "MAX";
 
  var Row = new commandRow(Commands,Names);
  CommandRowSx[9] = Row;
  
  // button row #0
  var Commands = new Array();
  var Names    = new Array();
  
  Commands[0] = "calcInv();";          Names[0] = "1/x";
  Commands[1] = "calcScie();";         Names[1] = "SCIE";
  Commands[2] = "calc000();";          Names[2] = "000";
  Commands[3] = "calcNeg();";          Names[3] = "+/-";
  Commands[4] = "addMem()";            Names[4] = "MEM";
  
  var Row = new commandRow(Commands,Names);
  CommandRowDx[0] = Row;
  
  // button row #1
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "addDigit(7);";        Names[0] = "<b>&nbsp;7&nbsp;</b>";
  Commands[1] = "addDigit(8);";        Names[1] = "<b>&nbsp;8&nbsp;</b>";
  Commands[2] = "addDigit(9);";        Names[2] = "<b>&nbsp;9&nbsp;</b>";
  Commands[3] = "calcDiv();";          Names[3] = "<b>&nbsp;&divide;&nbsp;</b>";
  Commands[4] = "calcDrop();";         Names[4] = "DROP";
  
  var Row = new commandRow(Commands,Names);
  CommandRowDx[1] = Row;
  
  // button row #2
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "addDigit(4);";        Names[0] = "<b>&nbsp;4&nbsp;</b>";
  Commands[1] = "addDigit(5);";        Names[1] = "<b>&nbsp;5&nbsp;</b>";
  Commands[2] = "addDigit(6);";        Names[2] = "<b>&nbsp;6&nbsp;</b>";
  Commands[3] = "calcMult();";         Names[3] = "<b>&nbsp;&times;&nbsp;</b>";
  Commands[4] = "calcClear();";        Names[4] = "CLEAR";
  
  var Row = new commandRow(Commands,Names);
  CommandRowDx[2] = Row;

  // button row #3
  var Commands = new Array();
  var Names    = new Array();

  Commands[0] = "addDigit(1);";        Names[0] = "<b>&nbsp;1&nbsp;</b>";
  Commands[1] = "addDigit(2);";        Names[1] = "<b>&nbsp;2&nbsp;</b>";
  Commands[2] = "addDigit(3);";        Names[2] = "<b>&nbsp;3&nbsp;</b>";
  Commands[3] = "calcSub();";          Names[3] = "<b>&nbsp;-&nbsp;</b>";
  Commands[4] = "calcSwap(1,2);";      Names[4] = "SWAP";
  
  var Row = new commandRow(Commands,Names);
  CommandRowDx[3] = Row;

  // button row #4
  var Commands = new Array();
  var Names    = new Array();
  
  Commands[0] = "addDigit(0);";        Names[0] = "<b>&nbsp;0&nbsp;</b>";
  Commands[1] = "addDigit(10);";       Names[1] = "<b>&nbsp;.&nbsp;</b>";
  Commands[2] = "calcEnter();";        Names[2] = "ENTER";
  Commands[3] = "calcAdd();";          Names[3] = "<b>&nbsp;+&nbsp;</b>";
  Commands[4] = "remDigit();";         Names[4] = "BACK";
  
  var Row = new commandRow(Commands,Names);
  CommandRowDx[4] = Row;
  
  // html line to select a different tool
  txt1 += toolSelector("document.getElementById('extra').innerHTML=''; ActiveTool='Week'; WeekFirst=1; showWeek();","Calculator V1.0","document.getElementById('extra').innerHTML=''; clearTimeout(Timer); ActiveTool='Themes'; showThemes();") + "<br>";

  // html code with the calulator buttons
  txt2 += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
  txt2 += "<a href='./doc/calc/calchelp.pdf' target='_blank'>HELP</a> | ";
  txt2 += "<a onclick=" + apx + "CalcModeAngle=(CalcModeAngle+1)%3; showCalculator();" + apx + " href='#'>" + ValModeAngle[CalcModeAngle] + "</a> | ";
  txt2 += "<a onclick=" + apx + "CalcModeKeep=(CalcModeKeep+1)%2; showCalculator();"   + apx + " href='#'>" + ValModeKeep[CalcModeKeep]   + "</a> | ";
  txt2 += "<a onclick=" + apx + "CalcModeStack=(CalcModeStack+1)%6; showCalculator();" + apx + " href='#'>" + ValModeStack[CalcModeStack] + "</a> | ";
  txt2 += "<a onclick=" + apx + "CalcModeComm=(CalcModeComm+9)%10; closeStack(); showCalculator();"  + apx + " href='#'>PREV</a> | ";
  txt2 += "<a onclick=" + apx + "CalcModeComm=(CalcModeComm+1)%10; closeStack(); showCalculator();"  + apx + " href='#'>NEXT</a>";
  txt2 += showMem();
  txt2 += "<div id=" + apx + "display" + apx + " align='right'></div><br>";
  txt2 += "<center>";
  txt2 += "<table>";
  txt2 += "<tr>";
  txt2 += "<td><table>";
  for (i=CalcModeComm; i<=CalcModeComm+4; i++) { txt2 += showCommandRow(CommandRowSx[i%10],65); }
  txt2 += "</table></td>";
  txt2 += "<td><table>";
  for (i=0; i<=4; i++) { txt2 += showCommandRow(CommandRowDx[i],35); }
  txt2 += "</table></td>";
  txt2 += "</tr>";
  txt2 += "</table>";
  txt2 += "</center>";
  txt2 += "<br>";
 
  // html update
  document.getElementById("toolbox").innerHTML=txt1;
  document.getElementById("extra").innerHTML=txt2;
  showDisplay(0);

  return null;
}

/* #####################################################################
 * html code for displaying a row of commands (inside a table)
 * ##################################################################### */

function showCommandRow(CommandRow,Space)
{
  var txt = "<tr height='25'>", apx='"';
  var i;
  
  for (i=0; i<CommandRow.Commands.length; i++)
  {
  	txt += "<td width='" + Space + "'><a onclick=" + apx + CommandRow.Commands[i]  + apx + " href=" + apx + "#" + apx + ">&nbsp;" + CommandRow.Names[i] + "&nbsp;</a></td>";
  }
  txt += "</tr>";

  return txt;
}

/* #####################################################################
 * Add a digit to the stack's lowest level
 * ##################################################################### */

function addDigit(Digit)
{
  if (CalcStackEmpty==1)
  {
  	var z = new cplx(0,0); 
    CalcStack.push(z);
    CalcStackEmpty=0;
  }
  if (Digit<=9 && CalcDecimal<13)
  {
    if (CalcDecimal==0) 
    {
      CalcStack[CalcStack.length-1].Re = CalcStack[CalcStack.length-1].Re*10+Digit;
    }
    else
    {
      CalcStack[CalcStack.length-1].Re = CalcStack[CalcStack.length-1].Re+Digit*Math.pow(10,-CalcDecimal);
      CalcDecimal++;
      CalcStack[CalcStack.length-1] = Round(CalcStack[CalcStack.length-1],CalcDecimal);
      if (Digit==0) { CalcTrailing0s++; }
      else          { CalcTrailing0s=0; }
    }
  }
  if (Digit==10 && CalcDecimal==0) // code for decimal separator
  {
    CalcDecimal=1;
  }
  showDisplay(1);

  return null;
}

/* #####################################################################
 * Add 3 zeros
 * ##################################################################### */

function calc000()
{
  addDigit(0);
  addDigit(0);
  addDigit(0);
  return null;
}

/* #####################################################################
 * Remove a digit to the stack's lowest level
 * ##################################################################### */

function remDigit()
{
  if (CalcStackEmpty==0)
  {
    // if the chopping involves the FP, then update the number of decimal digits
  	if (CalcDecimal > 0) { CalcDecimal--; }
  
  	if (CalcTrailing0s > 0) 
  	{
  	  CalcTrailing0s--;
  	}
  	else
  	{
   	  // number to string conversion
      var txt = CalcStack[CalcStack.length-1].Re + '';
    
      // chop out the last character of the string
      txt = txt.slice(0,txt.length-1);
    
      // if the string is nullified, put 0
      if (txt.length == 0) { txt='0'; }
      
      // count the number of trailing zeros
      if (CalcDecimal > 0)
      {
        i=txt.length; CalcTrailing0s=0;
        while (txt.charAt(i-1)=='0' && i>=0)
        {
      	  i--; CalcTrailing0s++;
        }
      }
    
      // string to number conversion
      CalcStack[CalcStack.length-1].Re = parseFloat(txt);
    }
    // that's all folks
    showDisplay(1);
  }

  return null;
}

/* #####################################################################
 * Update the calculator display
 * ##################################################################### */

function showDisplay(mode)
{
  // local variables
  var i;
  var txt="<table>";
  var point="";
  var zeros="";

  // assessed stack's levels
  for (i=0; i<CalcStack.length-1; i++)
  {
    txt += "<tr><td align='right'><b>" + cplxShow(CalcStack[i]) + "&nbsp;&nbsp;</b></td><td><a onclick='" + comModeStack(CalcStack.length-i) + "' href='#'>[" + (CalcStack.length-i) + "]</a></td><td width='40'></td></tr>";
  }

  // possibly active lowest stack's level  
  if (CalcStack.length>0) 
  {
    for (i=0; i<CalcTrailing0s; i++) { zeros += "0"; }
    if (CalcDecimal==1 || (CalcTrailing0s>0 && CalcDecimal==CalcTrailing0s+1)) { point="."; }
    if (mode==1)
    {
      txt += "<tr><td align='right'><b>" + CalcStack[CalcStack.length-1].Re + point + zeros + "&nbsp;&nbsp;&lt;&lt;&nbsp;&nbsp;</b></td><td>[1]</td><td width='40'></td></tr>";
    }
    else
    {
      txt += "<tr><td align='right'><b>" + cplxShow(CalcStack[CalcStack.length-1]) + "&nbsp;&nbsp;</b></td><td><a onclick='" + comModeStack(1) + "' href='#'>[1]</a></td><td width='40'></td></tr>";
    }
  }
  txt += "</table>";
  
  // update the display
  document.getElementById("display").innerHTML=txt;

  return null;
}

/* #####################################################################
 * Update the visualization of calculator memory
 * ##################################################################### */

function showMem()
{
  var txt = "";

  if (CalcMem.length > 0)
  {
  	txt += "<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MEMORY | ";
  	txt += "<a onclick='calcClearMem();' href='#'>CLEAR</a> | ";
  	if (CalcModeRename==0) { txt += "<a onclick='CalcModeRename=1; showCalculator();' href='#'>RENAME</a>"; }
  	else                   { txt += "<a onclick='CalcModeRename=0; showCalculator();' href='#'>OK</a>"; }
  	for (i=0; i<CalcMem.length; i++)
  	{
      txt += " : <a onclick='" + comModeMem(CalcMem.length-i) + "' href='#'>" + CalcMem[i].Name + "</a>";
  	}
  	txt += "</p>"
  }
  
  return txt;
}

/* #####################################################################
 * Add an item to the calculator memory
 * ##################################################################### */

function addMem()
{
  if (CalcStack.length>0) 
  { 
    var Name = 'M' + (CalcMem.length+1);
    var Value = CalcStack[CalcStack.length-1];
    var Mem = new mem(Name,Value);
    CalcMem.push(Mem);
    if (CalcModeKeep) { closeStack(); }
    else              { calcDrop(1); }
    showCalculator();
  }
  return null;
}

/* #####################################################################
 * Rename an item in the calculator memory
 * ##################################################################### */

function calcRenameMem(n)
{
  CalcMem[CalcMem.length-n].Name=prompt("Enter a name: ",CalcMem[CalcMem.length-n].Name);
  showCalculator();
  return null;
}

/* #####################################################################
 * Scientific notation
 * ##################################################################### */

function calcScie()
{
  if (CalcStack.length>1) 
  { 
  	if (isInteger(CalcStack[CalcStack.length-1])==1)
  	{
  	  var w = new cplx(CalcStack[CalcStack.length-2].Re*Math.pow(10,CalcStack[CalcStack.length-1].Re),CalcStack[CalcStack.length-2].Im*Math.pow(10,CalcStack[CalcStack.length-1].Re));
      Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: the exponent must be integer when the scientific notation is used');
  	}
  }
  return null;	
}
 
 /* #####################################################################
 * Visual formatting of a complex number
 * ##################################################################### */

function cplxShow(z)
{
  var re="", im="", plus="";
  
  if (z.Re==0 && z.Im==0) 
  {
  	re="0";
  }
  else
  {
  	if (z.Re!=0) { re = z.Re; }
	if (z.Im!=0) 
  	{ 
  	  if (z.Re==0)
  	  {
      	if (z.Im<0) { plus = " - "; }
  	  }
  	  else
      { 
        if (z.Im>0) { plus = " + "; } 
        else        { plus = " - "; } 
      }
      if (Math.abs(z.Im)!=1) { im = Math.abs(z.Im); }
      im += " i";
    }
  }
  
  return re + plus + im;
}

/* #####################################################################
 * Output a result into the stack, according to the keep/loose logic
 * ##################################################################### */

function Output(x,n)
{
  var i;
  if (CalcModeKeep)
  {
  	CalcStack.push(Round(x,13));
  }
  else
  {
	CalcStack[CalcStack.length-n]=Round(x,13);
	for (i=1; i<n; i++) { CalcStack.pop(); }
  }
  closeStack();
  showDisplay(0);
  return null  
}

/* #####################################################################
 * Stack manipulation command, according to the stack manipulation mode
 * ##################################################################### */

function comModeStack(n)
{
	var command;
	var m=n+1; if (m>CalcStack.length) { m=1; }
	
	if (CalcModeStack==0) { command="calcPick(" + n + ");"; }
	if (CalcModeStack==1) { command="calcRoll(" + n + ",0);"; }
	if (CalcModeStack==2) { command="calcRoll(" + n + ",1);"; }
	if (CalcModeStack==3) { command="calcSwap(" + n + "," + m + ");"; }
	if (CalcModeStack==4) { command="calcDupl(" + n + ");"; }
	if (CalcModeStack==5) { command="calcDrop(" + n + ");"; }
	
	return command;
}

/* #####################################################################
 * Memory manipulation command, according to the stack manipulation mode
 * ##################################################################### */

function comModeMem(n)
{
	var command;
	var m=n+1; if (m>CalcMem.length) { m=1; }
	
	if (CalcModeRename==0)
	{
	  if (CalcModeStack==0) { command="calcPickMem(" + n + ");"; }
	  if (CalcModeStack==1) { command="calcRollMem(" + n + ",0);"; }
	  if (CalcModeStack==2) { command="calcRollMem(" + n + ",1);"; }
	  if (CalcModeStack==3) { command="calcSwapMem(" + n + "," + m + ");"; }
	  if (CalcModeStack==4) { command="calcDuplMem(" + n + ");"; }
	  if (CalcModeStack==5) { command="calcDropMem(" + n + ");"; }
	}
	else
	{
	  command="calcRenameMem(" + n + ");";
	}
	
	return command;
}

/* #####################################################################
 * Stop edit mode for the stack's the lowest level
 * ##################################################################### */

function closeStack()
{
  CalcStackEmpty=1; 
  CalcDecimal=0;
  CalcTrailing0s=0;

  return null;
}

/* #####################################################################
 * ENTER command
 * ##################################################################### */

function calcEnter()
{
  if (CalcStackEmpty==0) { closeStack(); }
  else                   { CalcStack.push(CalcStack[CalcStack.length-1]); }
  showDisplay(0);
  return null;
}

/* #####################################################################
 * DROP command
 * ##################################################################### */

function calcDrop(n)
{
  calcSwap(1,n); 
  CalcStack.pop();
  if (n>1) { calcSwap(1,n-1); }
  
  closeStack();
  showDisplay(0);

  return null;
}

function calcDropMem(n)
{
  calcSwapMem(1,n); 
  CalcMem.pop();
  if (n>1) { calcSwapMem(1,n-1); }
  
  showCalculator();

  return null;
}

/* #####################################################################
 * CLEAR command
 * ##################################################################### */

function calcClear()
{
  // local variables
  var i;
  var n=CalcStack.length;
  
  for (i=0; i<n; i++) { CalcStack.pop(); } 
  showDisplay(0);

  return null;
}

function calcClearMem()
{
  // local variables
  var i;
  var n=CalcMem.length;
  
  for (i=0; i<n; i++) { CalcMem.pop(); } 
  showCalculator();

  return null;
}

/* #####################################################################
 * SWAP command
 * ##################################################################### */

function calcSwap(m,n)
{
  var temp=CalcStack[CalcStack.length-m];  

  if (CalcStack.length>=Math.max(m,n))
  {
    CalcStack[CalcStack.length-m]=CalcStack[CalcStack.length-n];
    CalcStack[CalcStack.length-n]=temp;
    closeStack();
    showDisplay(0);
  }
  return null;
}

function calcSwapMem(m,n)
{
  var temp=CalcMem[CalcMem.length-m];  
  if (CalcMem.length>=Math.max(m,n))
  {
    CalcMem[CalcMem.length-m]=CalcMem[CalcMem.length-n];
    CalcMem[CalcMem.length-n]=temp;
    showCalculator();
  }
  return null;
}

/* #####################################################################
 * ROLL command
 * ##################################################################### */

function calcRoll(n,d)
{
  var i;

  if (d==0) { for (i=n; i>1; i--) { calcSwap(i,i-1); } }
  if (d==1) { for (i=1; i<n; i++) { calcSwap(i,i+1); } }
  
  closeStack();
  showDisplay(0);
  
  return null;
}

function calcRollMem(n,d)
{
  var i;

  if (d==0) { for (i=n; i>1; i--) { calcSwapMem(i,i-1); } }
  if (d==1) { for (i=1; i<n; i++) { calcSwapMem(i,i+1); } }
  
  showCalculator();
  
  return null;
}

/* #####################################################################
 * PICK command
 * ##################################################################### */

function calcPick(n)
{
  CalcStack.push(CalcStack[CalcStack.length-n]);  

  closeStack();
  showDisplay(0);
  
  return null;
}

function calcPickMem(n)
{
  CalcStack.push(CalcMem[CalcMem.length-n].Value);  
  showCalculator();
  
  return null;
}

/* #####################################################################
 * DUPL command
 * ##################################################################### */

function calcDupl(n)
{
  var i;
  
  CalcStack.push(CalcStack[CalcStack.length-n]);
  for (i=1; i<n; i++) { calcSwap(i,i+1); }

  closeStack();
  showDisplay(0);
  
  return null;
}

function calcDuplMem(n)
{
  var i;
  
  CalcMem.push(CalcMem[CalcMem.length-n]);
  for (i=1; i<n; i++) { calcSwapMem(i,i+1); }

  showCalculator();
  
  return null;
}

/* #####################################################################
 * NEG command
 * ##################################################################### */

function calcNeg()
{
  if (CalcStack.length>0) { Output(Neg(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Neg(z)
{
  var w = new cplx(-z.Re,-z.Im);
  return w;
}

/* #####################################################################
 * Increment and decrement command
 * ##################################################################### */

function calcIncr()
{
  if (CalcStack.length>0) { Output(Incr(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Incr(z)
{
  var z1 = new cplx(1,0)	
  return Add(z,z1);
}

function calcDecr()
{
  if (CalcStack.length>0) { Output(Decr(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Decr(z)
{
  var z1 = new cplx(1,0)	
  return Sub(z,z1);
}

 /* #####################################################################
 * INV command
 * ##################################################################### */

function calcInv()
{
  if (CalcStack.length>0) { Output(Inv(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Inv(z)
{
  return Div(XY2cplx(1,0),z);
}

/* #####################################################################
 * ABS command
 * ##################################################################### */

function calcAbs()
{
  if (CalcStack.length>0) { Output(Abs(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Abs(z)
{
  var w = new cplx(Math.sqrt(z.Re*z.Re+z.Im*z.Im),0);
  return w;
}

/* #####################################################################
 * SIGN command
 * ##################################################################### */

function calcSign()
{
  if (CalcStack.length>0) { Output(Sign(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sign(z)
{
  var x,y;
  if (z.Re==0) { x=0; }
  else         { x=Math.abs(z.Re)/z.Re; }
  if (z.Im==0) { y=0; }
  else         { y=Math.abs(z.Im)/z.Im; }
  var w = new cplx(x,y); 
  return w;
}

/* #####################################################################
 * Real test
 * ##################################################################### */

function isReal(n)
{
  var res = 1;
  if (Math.abs(n.Im)     > CalcTolerance) { res = 0; }
  return res;
}

/* #####################################################################
 * Integer test
 * ##################################################################### */

function isInteger(n)
{
  var res = 1;
  if (Math.abs(n.Im)     > CalcTolerance) { res = 0; }
  if (Math.abs(FP(n).Re) > CalcTolerance) { res = 0; }
  return res;
}

/* #####################################################################
 * Natural test
 * ##################################################################### */

function isNatural(n)
{
  var res = isInteger(n);
  if (n.Re < 0) { res = 0; }
  return res;
}

/* #####################################################################
 * RND command
 * ##################################################################### */

function calcRnd()
{
  // local variables
  var n;
  var z;

  if (CalcStack.length>1)
  {
    z=CalcStack[CalcStack.length-2];
    n=Math.floor(Math.abs(CalcStack[CalcStack.length-1].Re));
    Output(Round(z,n),2);
  }
  return null;
}

function Round(z,n)
{
  var re = z.Re;

  re=re*Math.pow(10,n);
  re=Math.round(re);
  re=re/Math.pow(10,n);

  var im = z.Im;

  im=im*Math.pow(10,n);
  im=Math.round(im);
  im=im/Math.pow(10,n);
  
  var w = new cplx(re,im);
  
  return w;
}

/* #####################################################################
 * TRUNC command
 * ##################################################################### */

function calcTrunc()
{
  var n;
  if (CalcStack.length>1)
  {
    n=Math.floor(Math.abs(CalcStack[CalcStack.length-1].Re));
    Output(Trunc(CalcStack[CalcStack.length-2],n),2);
  }
  return null;
}

function Trunc(z,n)
{
  var re = z.Re;
  if (re > 0)
  {
    re*=Math.pow(10,n);
    re=Math.floor(re);
    re/=Math.pow(10,n);
  }
  else
  {
  	re*=Math.pow(10,n);
    re=Math.ceil(re);
    re/=Math.pow(10,n);
  }

  var im = z.Im;
  if (im > 0)
  {
    im*=Math.pow(10,n);
    im=Math.floor(im);
    im/=Math.pow(10,n);
  }
  else
  {
  	im*=Math.pow(10,n);
    im=Math.ceil(im);
    im/=Math.pow(10,n);
  }
  
  var w = new cplx(re,im);
  
  return w;
}

/* #####################################################################
 * CEIL command
 * ##################################################################### */

function calcCeil()
{
  if (CalcStack.length>0) { Output(Ceil(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Ceil(z)
{
  var w = new cplx(Math.ceil(z.Re),Math.ceil(z.Im));
  return w;
}

/* #####################################################################
 * FLOOR command
 * ##################################################################### */

function calcFloor()
{
  if (CalcStack.length>0) { Output(Floor(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Floor(z)
{
  var w = new cplx(Math.floor(z.Re),Math.floor(z.Im));
  return w;
}

/* #####################################################################
 * IP command
 * ##################################################################### */

function calcIp()
{
  if (CalcStack.length>0) { Output(IP(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function IP(z)
{
  return Trunc(z,0);
}

/* #####################################################################
 * FP command
 * ##################################################################### */

function calcFp()
{
  if (CalcStack.length>0) { Output(FP(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function FP(z)
{
  return Sub(z,IP(z));
}

/* #####################################################################
 * SUM command
 * ##################################################################### */

function calcAdd()
{
  if (CalcStack.length>1)
  {
    Output(Add(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Add(z1,z2)
{
  var w = new cplx(z1.Re+z2.Re,z1.Im+z2.Im);
  return w;
}

/* #####################################################################
 * SUBTRACTION command
 * ##################################################################### */

function calcSub()
{
  if (CalcStack.length>1)
  {
    Output(Sub(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Sub(z1,z2)
{
  var w = new cplx(z1.Re-z2.Re,z1.Im-z2.Im);
  return w;
}

/* #####################################################################
 * MULTIPLICATION command
 * ##################################################################### */

function calcMult()
{
  if (CalcStack.length>1)
  {
    Output(Mult(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Mult(z1,z2)
{
  var w = new cplx(z1.Re*z2.Re-z1.Im*z2.Im,z1.Re*z2.Im+z2.Re*z1.Im);
  return w;
}

/* #####################################################################
 * DIVISION command
 * ##################################################################### */

function calcDiv()
{
  if (CalcStack.length>1)
  {
    Output(Div(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Div(z1,z2)
{
  var m = z2.Re*z2.Re+z2.Im*z2.Im;
  var w = new cplx((z1.Re*z2.Re+z1.Im*z2.Im)/m,(z2.Re*z1.Im-z1.Re*z2.Im)/m);
  return w;
}

/* #####################################################################
 * Real part command
 * ##################################################################### */

function calcRe()
{
  if (CalcStack.length>0)
  {
    Output(Re(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Re(z)
{
  var w = new cplx(z.Re,0);
  return w;
}

/* #####################################################################
 * Imaginary part command
 * ##################################################################### */

function calcIm()
{
  if (CalcStack.length>0)
  {
    Output(Im(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Im(z)
{
  var w = new cplx(z.Im,0);
  return w;
}

/* #####################################################################
 * Argument command
 * ##################################################################### */

function calcArg()
{
  if (CalcStack.length>0)
  {
    Output(Arg(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Arg(z)
{
  var w = new cplx(Math.atan2(z.Im,z.Re),0);
  return w;
}

/* #####################################################################
 * Complex number from real and imaginary parts
 * ##################################################################### */

function calcXY2cplx()
{
  if (CalcStack.length>1)
  {
    Output(XY2cplx(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),2);
  }
  return null;
}

function XY2cplx(x,y)
{
  var w = new cplx(x,y);
  return w;
}

/* #####################################################################
 * Complex number from modulus and argument
 * ##################################################################### */

function calcRhoArg2cplx()
{
  if (CalcStack.length>1)
  {
    Output(RhoArg2cplx(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),2);
  }
  return null;
}

function RhoArg2cplx(rho,arg)
{
  var w = new cplx(rho*Math.cos(arg),rho*Math.sin(arg));
  return w;
}

/* #####################################################################
 * Conjugate command
 * ##################################################################### */

function calcConj()
{
  if (CalcStack.length>0)
  {
    Output(Conj(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Conj(z)
{
  var w = new cplx(z.Re,-z.Im);
  return w;
}

/* #####################################################################
 * Solution of algebraic equation of order 2
 * ##################################################################### */

function calcEqn2()
{
  if (CalcStack.length>2)
  {
  	var a = CalcStack[CalcStack.length-3];
  	var b = CalcStack[CalcStack.length-2];
  	var c = CalcStack[CalcStack.length-1];
  	
  	var z2 = new cplx(2,0);
  	var z4 = new cplx(4,0);
  	
  	var delta = Sub(Square(b),Mult(z4,Mult(a,c)));
  	var x1 = Div(Sub(Neg(b),Sqrt(delta)),Mult(z2,a));
  	var x2 = Div(Add(Neg(b),Sqrt(delta)),Mult(z2,a));
    
    Output(x1,3);
    Output(x2,0);
  }
  return null;
}

/* #####################################################################
 * Solution of algebraic equation of order 3
 * ##################################################################### */

function calcEqn3()
{
  if (CalcStack.length>3)
  {
  	var a = CalcStack[CalcStack.length-4];
  	var b = CalcStack[CalcStack.length-3];
  	var c = CalcStack[CalcStack.length-2];
  	var d = CalcStack[CalcStack.length-1];
  	
  	var z2  = new cplx(2,0);
  	var z3  = new cplx(3,0);
  	var z4  = new cplx(4,0);
  	var z9  = new cplx(9,0);
  	var z27 = new cplx(27,0);
  	
  	var a3  = Mult(z3,a);
    var m   = Neg(Inv(a3));
  	var n1  = Add(Sub(Mult(z2,Cube(b)),Mult(Mult(z9,a),Mult(b,c))),Mult(Mult(z27,Square(a)),d));
    var n2  = Mult(z4,Cube(Sub(Square(b),Mult(a3,c)))); 
    var r2  = Sqrt(Sub(Square(n1),n2));
    var r3p = Cbrt(Div(Add(n1,r2),z2)); 
    var r3n = Cbrt(Div(Sub(n1,r2),z2)); 
    
    var w1  = new cplx(-1/2, Math.sqrt(3)/2);
    var w2  = new cplx(-1/2,-Math.sqrt(3)/2);
  	
  	var x1 = Mult(m,Add(Add(b,r3p),r3n));
  	var x2 = Mult(m,Add(Add(b,Mult(w2,r3p)),Mult(w1,r3n)));
  	var x3 = Mult(m,Add(Add(b,Mult(w1,r3p)),Mult(w2,r3n)));
    
    Output(x1,4);
    Output(x2,0);
    Output(x3,0);
  }
  return null;
}

/* #####################################################################
 * Solution of algebraic equation of order 4
 * ##################################################################### */

function calcEqn4()
{
  if (CalcStack.length>4)
  {
  	var a = CalcStack[CalcStack.length-5];
  	var b = CalcStack[CalcStack.length-4];
  	var c = CalcStack[CalcStack.length-3];
  	var d = CalcStack[CalcStack.length-2];
  	var e = CalcStack[CalcStack.length-1];
  	
  	var z2   = new cplx(2,0);
  	var z3   = new cplx(3,0);
  	var z4   = new cplx(4,0);
  	var z8   = new cplx(8,0);
  	var z12  = new cplx(12,0);
  	var z16  = new cplx(16,0);
  	var z27  = new cplx(27,0);
  	var z108 = new cplx(108,0);
  	var z256 = new cplx(256,0);
  	var z5d6 = new cplx(5/6,0);

  	var alpha = Sub(Div(c,a),Div(Mult(z3,Square(b)),Mult(z8,Square(a))));
  	var beta  = Add(Sub(Div(Cube(b),Mult(z8,Cube(a))),Div(Mult(b,c),Mult(z2,Square(a)))),Div(d,a));
  	var gamma1 = Neg(Div(Mult(z3,Square(Square(b))),Mult(z256,Square(Square(a)))));
  	var gamma2 = Div(Mult(c,Square(b)),Mult(z16,Cube(a)));
  	var gamma3 = Neg(Div(Mult(b,d),Mult(z4,Square(a))));
  	var gamma4 = Div(e,a);
  	var gamma = Add(Add(gamma1,gamma2),Add(gamma3,gamma4));

  	if (beta.Re*beta.Re+beta.Im*beta.Im < 10*CalcTolerance)
  	{
      var rad1 = Sqrt(Sub(Square(alpha),Mult(z4,gamma)));
      var rad1p = Sqrt(Div(Add(Neg(alpha),rad1),z2));
      var rad1m = Sqrt(Div(Sub(Neg(alpha),rad1),z2));

      var x1 = Add(Div(Neg(b),Mult(z4,a)),rad1p);
      var x2 = Add(Div(Neg(b),Mult(z4,a)),rad1m);
      var x3 = Sub(Div(Neg(b),Mult(z4,a)),rad1p);
      var x4 = Sub(Div(Neg(b),Mult(z4,a)),rad1m);
  	}
  	else
  	{
  	  var P = Sub(Neg(gamma),Div(Square(alpha),z12));
  	  var Q = Sub(Add(Neg(Div(Cube(alpha),z108)),Div(Mult(alpha,gamma),z3)),Div(Square(beta),z8));
  	  var R = Add(Neg(Div(Q,z2)),Sqrt(Add(Div(Square(Q),z4),Div(Cube(P),z27))));
  	  var U = Cbrt(R);
  	  
  	  if (U.Re*U.Re+U.Im*U.Im < 10*CalcTolerance)
  	  {
  	  	var y = Sub(Sub(U,Mult(z5d6,alpha)),Cbrt(Q));
      }
  	  else
  	  {
  	  	var y = Sub(Sub(U,Mult(z5d6,alpha)),Div(P,Mult(z3,U)));
  	  }
  	  
  	  var W = Sqrt(Add(alpha,Mult(z2,y)));

  	  var rad1p = Sqrt(Neg(Add(Add(Mult(z3,alpha),Mult(z2,y)),Div(Mult(z2,beta),W))));
  	  var rad1m = Sqrt(Neg(Sub(Add(Mult(z3,alpha),Mult(z2,y)),Div(Mult(z2,beta),W))));
  	  
  	  var rat1pm = Div(Sub(W,rad1p),z2);
  	  var rat1pp = Div(Add(W,rad1p),z2);
  	  var rat1mm = Div(Sub(Neg(W),rad1m),z2);
  	  var rat1mp = Div(Add(Neg(W),rad1m),z2);
  	  
      var x1 = Add(Div(Neg(b),Mult(z4,a)),rat1pm);
      var x2 = Add(Div(Neg(b),Mult(z4,a)),rat1pp);
      var x3 = Add(Div(Neg(b),Mult(z4,a)),rat1mm);
      var x4 = Add(Div(Neg(b),Mult(z4,a)),rat1mp);
  	}
  	
  	Output(x1,5);
    Output(x2,0);
    Output(x3,0);
    Output(x4,0);
  }
  return null;
}
 

/* #####################################################################
 * MOD command
 * ##################################################################### */

function calcMod()
{
  if (CalcStack.length>1)
  {
  	if (isInteger(CalcStack[CalcStack.length-2])==1 && isInteger(CalcStack[CalcStack.length-1])==1)
  	{
  	  var w = new cplx(CalcStack[CalcStack.length-2].Re % CalcStack[CalcStack.length-1].Re,0);
      Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be integer');
  	}
  }
  return null;
}

/* #####################################################################
 * INTDIV command
 * ##################################################################### */

function calcIntDiv()
{
  if (CalcStack.length>1)
  {
  	if (isInteger(CalcStack[CalcStack.length-2])==1 && isInteger(CalcStack[CalcStack.length-1])==1)
  	{
  	  var w = new cplx((CalcStack[CalcStack.length-2].Re - CalcStack[CalcStack.length-2].Re % CalcStack[CalcStack.length-1].Re) / CalcStack[CalcStack.length-1].Re,0);
      Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be integer');
  	}
  }
  return null;
}

/* #####################################################################
 * GCD command
 * ##################################################################### */

function calcGCD()
{
  if (CalcStack.length>1)
  {
  	if (isNatural(CalcStack[CalcStack.length-2]) && isNatural(CalcStack[CalcStack.length-1]))
  	{
  	  var w = new cplx(GCD(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),0);
      Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be natural numbers');
  	}
  }
  return null;
}

function GCD(a,b)
{
  var t;
  while (b != 0) 
  {
    t = b;
    b = a % b;
    a = t;
  }
  return a;
}

/* #####################################################################
 * LCM command
 * ##################################################################### */

function calcLCM()
{
  if (CalcStack.length>1)
  {
  	if (isNatural(CalcStack[CalcStack.length-2]) && isNatural(CalcStack[CalcStack.length-1]))
  	{
  	  var w = new cplx(LCM(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),0);
      Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be natural numbers');
  	}
  }
  return null;
}

function LCM(a,b)
{
  return a*b/GCD(a,b);
}

/* #####################################################################
 * MIN command
 * ##################################################################### */

function calcMin()
{
  if (CalcStack.length>1)
  {
  	if (isReal(CalcStack[CalcStack.length-2])==1 && isReal(CalcStack[CalcStack.length-1])==1)
  	{
  	  var w = new cplx(Math.min(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),0);
  	  Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be real');
  	}
  }
  return null;
}

/* #####################################################################
 * MAX command
 * ##################################################################### */

function calcMax()
{
  if (CalcStack.length>1)
  {
  	if (isReal(CalcStack[CalcStack.length-2])==1 && isReal(CalcStack[CalcStack.length-1])==1)
  	{
  	  var w = new cplx(Math.max(CalcStack[CalcStack.length-2].Re,CalcStack[CalcStack.length-1].Re),0);
  	  Output(w,2);
  	}
  	else
  	{
  	  alert('ERROR: operands must be real');
  	}
  }
  return null;
}

/* #####################################################################
 * Euler Gamma function command
 * ##################################################################### */

function calcEulerGamma()
{
  if (CalcStack.length>0) { Output(EulerGamma(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function EulerGamma(z)
{
  var z1 = new cplx(1,0);
  var reflex = 0;
  var zorig = z;
  
  // reflection formula
  if (z.Re < 0.5) { z=Sub(z1,z); reflex=1; }

  // standard computation
  z=Decr(z);
  
  var z2pi = new cplx(2*Math.PI,0);
  var z1s2 = new cplx(0.5,0);
  var g    = new cplx(7,0);
  var t    = Add(Add(z,g),z1s2);
  
  var p    = new Array();
  p[0]=    0.99999999999980993;
  p[1]=  676.5203681218851;
  p[2]=-1259.1392167224028;
  p[3]=  771.32342877765313;
  p[4]= -176.61502916214059;
  p[5]=   12.507343278686905;
  p[6]=   -0.13857109526572012;
  p[7]=    9.9843695780195716e-6;
  p[8]=    1.5056327351493116e-7;
  
  var A = new cplx(p[0],0);
  for (i=1; i<7+2; i++) 
  { 
  	var c = new cplx(p[i],0);
  	var j = new cplx(i,0);
    A = Add(A,Div(c,Add(z,j))); 
  }
  var w = Mult(Mult(Mult(Sqrt(z2pi),Pow(t,Add(z,z1s2))),Exp(Neg(t))),A);
  
  // reflection formula
  if (reflex==1) { w=Div(Pi(),Mult(Sin(Mult(Pi(),zorig)),w)); }

  return w;
}

/* #####################################################################
 * Log Gamma function command
 * ##################################################################### */

function calcLnGamma()
{
  if (CalcStack.length>0) { Output(LnGamma(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function LnGamma(z)
{
  return Ln(EulerGamma(z));
}

/* #####################################################################
 * Beta function command
 * ##################################################################### */

function calcBeta()
{
  if (CalcStack.length>1) 
  { 
  	if (CalcStack[CalcStack.length-2].Re>0 && CalcStack[CalcStack.length-1].Re>0)
  	{
      Output(Beta(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  	}
  }
  return null;
}

function Beta(x,y)
{
  return Div(Mult(EulerGamma(x),EulerGamma(y)),EulerGamma(Add(x,y)));
}

/* #####################################################################
 * Log Beta function command
 * ##################################################################### */

function calcLnBeta()
{
  if (CalcStack.length>1) 
  { 
  	if (CalcStack[CalcStack.length-2].Re>0 && CalcStack[CalcStack.length-1].Re>0)
  	{
      Output(LnBeta(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  	}
  }
  return null;
}

function LnBeta(x,y)
{
  return Ln(Div(Mult(EulerGamma(x),EulerGamma(y)),EulerGamma(Add(x,y))));
}

/* #####################################################################
 * FACT command
 * ##################################################################### */

function calcFact()
{
  if (CalcStack.length>0) { Output(Fact(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Fact(n)
{
  if (isInteger(n) && n.Re>=0)
  {
  	// naive implementation of the factorial for positive integers
  	var m=1,i;
    for (i=2; i<=n.Re; i++) { m *= i; }
    var w = new cplx(m,0);
  }
  else
  {
  	// by means of the Euler Gamma function otherwise
  	var w = EulerGamma(Incr(n));
  }
  return w;
}

/* #####################################################################
 * Binomial coefficent command
 * ##################################################################### */

function calcBinomial()
{
  if (CalcStack.length>1)
  {
  	Output(Binomial(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Binomial(n,k)
{
  if (isNatural(n) & isNatural(k) & n.Re>k.Re)
  {
  	// traditional way
    var i, N=n.Re, K=k.Re, res=N;
    if (K > N/2) { K=N-K; }
  	for (i=1; i<K; i++) { res*=(N-i); }
  	var b = new cplx(res,0);
  	b=Div(b,Fact(k));
    return b;
  }
  else
  {
  	// by means of Euler Gamma function
  	return Exp(Sub(Sub(LnGamma(Incr(n)),LnGamma(Incr(k))),LnGamma(Incr(Sub(n,k)))));
  }
  	
}

/* #####################################################################
 * SQRT command
 * ##################################################################### */

function calcSqrt()
{
  if (CalcStack.length>0) { Output(Sqrt(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sqrt(z)
{
  var rho = Math.sqrt(Abs(z).Re);
  var arg = Arg(z).Re/2;
  return RhoArg2cplx(rho,arg);
}

/* #####################################################################
 * CBRT command (cubic root)
 * ##################################################################### */

function calcCbrt()
{
  if (CalcStack.length>0) { Output(Cbrt(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Cbrt(z)
{
  var rho = Math.pow(Abs(z).Re,1/3);
  var arg = Arg(z).Re/3;
  return RhoArg2cplx(rho,arg);
}

/* #####################################################################
 * ROOT command
 * ##################################################################### */

function calcRoot()
{
  if (CalcStack.length>1)
  {
  	Output(Root(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Root(z,w)
{
  return Pow(z,Inv(w));
}

/* #####################################################################
 * UROOT command
 * ##################################################################### */

function calcUroot()
{
  if (CalcStack.length>0)
  {
  	var w = CalcStack[CalcStack.length-1];
  	if (isInteger(w)==1)
  	{
  	  var n = IP(w).Re;
  	  if (n > 0)
  	  {
  	  	var z1 = new cplx(1,0);
  	  	var ang = 2*Math.PI/n;
        Output(z1,1);
  	    for (i=1; i<n; i++)
  	    {
          var z = new cplx(Math.cos(i*ang),Math.sin(i*ang));
  	      Output(z,0);
  	    }
  	  }
  	}
  }
  return null;
}

/* #####################################################################
 * Square command
 * ##################################################################### */

function calcSquare()
{
  if (CalcStack.length>0)
  {
  	Output(Square(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Square(z)
{
  return Mult(z,z);
}

/* #####################################################################
 * Cube command
 * ##################################################################### */

function calcCube()
{
  if (CalcStack.length>0)
  {
  	Output(Cube(CalcStack[CalcStack.length-1]),1);
  }
  return null;
}

function Cube(z)
{
  return Mult(Mult(z,z),z);
}

/* #####################################################################
 * POW command
 * ##################################################################### */

function calcPow()
{
  if (CalcStack.length>1)
  {
  	Output(Pow(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Pow(z,w)
{
  return Exp(Mult(w,Ln(z)));
}

/* #####################################################################
 * POW10 command
 * ##################################################################### */

function calcPow10()
{
  if (CalcStack.length>0) { Output(Pow10(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Pow10(z)
{
  var w = new cplx(Math.log(10),0);
  return Exp(Mult(z,w));	
}

/* #####################################################################
 * EXP command
 * ##################################################################### */

function calcExp()
{
  if (CalcStack.length>0) { Output(Exp(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Exp(z)
{
  return RhoArg2cplx(Math.exp(z.Re),z.Im);
}

/* #####################################################################
 * LN (Neperian, e base) command
 * ##################################################################### */

function calcLn()
{
  if (CalcStack.length>0) { Output(Ln(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Ln(z)
{
  var rho = Abs(z).Re;
  var ang = Arg(z).Re;
  var w = new cplx(Math.log(rho),ang);
  return w;
}

/* #####################################################################
 * LOG (Briggs, 10 base) command
 * ##################################################################### */

function calcLog()
{
  if (CalcStack.length>0) { Output(Log(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Log(z)
{
  var z10 = new cplx(10,0);	
  return Div(Ln(z),Ln(z10));
}

/* #####################################################################
 * LOGb (universal, general base b) command
 * ##################################################################### */

function calcLogb()
{
  if (CalcStack.length>1)
  {
  	Output(Logb(CalcStack[CalcStack.length-2],CalcStack[CalcStack.length-1]),2);
  }
  return null;
}

function Logb(z,b)
{
  return Div(Ln(z),Ln(b));
}

/* #####################################################################
 * SIN command
 * ##################################################################### */

function calcSin()
{
  if (CalcStack.length>0) { Output(Sin(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sin(z)
{
  if (z.Im==0)
  {
    var angle = z.Re;
  	if (CalcModeAngle==1) { angle*=Math.PI/180; }
  	if (CalcModeAngle==2) { angle*=Math.PI/200; }
  	var w = new cplx(Math.sin(angle),0);
  	return w;
  }
  else
  {
  	var i = new cplx(0,1);
  	return Div(Sinh(Mult(i,z)),i);
  }
}

/* #####################################################################
 * COS command
 * ##################################################################### */

function calcCos()
{
  if (CalcStack.length>0) { Output(Cos(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Cos(z)
{
  if (z.Im==0)
  {
    var angle = z.Re;
  	if (CalcModeAngle==1) { angle*=Math.PI/180; }
  	if (CalcModeAngle==2) { angle*=Math.PI/200; }
    var w = new cplx(Math.cos(angle),0);
  	return w;
  }
  else
  {
  	var i = new cplx(0,1);
  	return Cosh(Mult(i,z));
  }
}

/* #####################################################################
 * TAN command
 * ##################################################################### */

function calcTan()
{
  if (CalcStack.length>0) { Output(Tan(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Tan(z)
{
  return Div(Sin(z),Cos(z));
}

/* #####################################################################
 * CSC command
 * ##################################################################### */

function calcCsc()
{
  if (CalcStack.length>0) { Output(Csc(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Csc(z)
{
  return Inv(Sin(z));
}

/* #####################################################################
 * SEC command
 * ##################################################################### */

function calcSec()
{
  if (CalcStack.length>0) { Output(Sec(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sec(z)
{
  return Inv(Cos(z));
}

/* #####################################################################
 * COT command
 * ##################################################################### */

function calcCot()
{
  if (CalcStack.length>0) { Output(Cot(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Cot(z)
{
  return Inv(Tan(z));
}

/* #####################################################################
 * ASIN command
 * ##################################################################### */

function calcAsin()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Asin(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Asin(z)
{
  var i = new cplx(0,1);	
  return Mult(Neg(i),Asinh(Mult(i,z)));	
}

/* #####################################################################
 * ACOS command
 * ##################################################################### */

function calcAcos()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Acos(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Acos(z)
{
  var pi2 = new cplx(Math.PI/2,0);	
  return Sub(pi2,Asin(z));	
}

/* #####################################################################
 * ATAN command
 * ##################################################################### */

function calcAtan()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Atan(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Atan(z)
{
  var i  = new cplx(0,1);	
  var z1 = new cplx(1,0);
  var z2 = new cplx(2,0);
  var w  = Mult(i,z);
  return Mult(Div(i,z2),Sub(Ln(Sub(z1,w)),Ln(Incr(w))));	
}

/* #####################################################################
 * ACSC command
 * ##################################################################### */

function calcAcsc()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Acsc(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Acsc(z)
{
  var i  = new cplx(0,1);	
  var z1 = new cplx(1,0);
  return Mult(Neg(i),Ln(Add(Sqrt(Sub(z1,Inv(Square(z)))),Div(i,z))));	
}

/* #####################################################################
 * ASEC command
 * ##################################################################### */

function calcAsec()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Asec(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Asec(z)
{
  var pi2 = new cplx(Math.PI/2,0);	
  return Sub(pi2,Acsc(z));	
}

/* #####################################################################
 * ACOT command
 * ##################################################################### */

function calcAcot()
{
  var angle;
  if (CalcStack.length>0)
  {
  	angle=Acot(CalcStack[CalcStack.length-1]);
  	if (Math.abs(angle.Im)<CalcTolerance)
  	{
  	  angle.Im=0;	
  	  if (CalcModeAngle==1) { angle.Re/=Math.PI/180; }
  	  if (CalcModeAngle==2) { angle.Re/=Math.PI/200; }
  	}
    Output(angle,1);
  }
  return null;
}

function Acot(z)
{
  var i  = new cplx(0,1);	
  var z1 = new cplx(1,0);
  var z2 = new cplx(2,0);
  var w  = Div(i,z);
  return Mult(Div(i,z2),Sub(Ln(Sub(z1,w)),Ln(Incr(w))));	
}

/* #####################################################################
 * SINH command
 * ##################################################################### */

function calcSinh()
{
  if (CalcStack.length>0) { Output(Sinh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sinh(z)
{
  var z2 = new cplx(2,0);
  return Div(Sub(Exp(z),Exp(Neg(z))),z2);
}

/* #####################################################################
 * COSH command
 * ##################################################################### */

function calcCosh()
{
  if (CalcStack.length>0) { Output(Cosh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Cosh(z)
{
  var z2 = new cplx(2,0);
  return Div(Add(Exp(z),Exp(Neg(z))),z2);
}

/* #####################################################################
 * TANH command
 * ##################################################################### */

function calcTanh()
{
  if (CalcStack.length>0) { Output(Tanh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Tanh(z)
{
  return Div(Sinh(z),Cosh(z));
}

/* #####################################################################
 * CSCH command
 * ##################################################################### */

function calcCsch()
{
  if (CalcStack.length>0) { Output(Csch(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Csch(z)
{
  return Inv(Sinh(z));
}

/* #####################################################################
 * SECH command
 * ##################################################################### */

function calcSech()
{
  if (CalcStack.length>0) { Output(Sech(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Sech(z)
{
  return Inv(Cosh(z));
}

/* #####################################################################
 * COTH command
 * ##################################################################### */

function calcCoth()
{
  if (CalcStack.length>0) { Output(Coth(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Coth(z)
{
  return Div(Cosh(z),Sinh(z));
}

/* #####################################################################
 * ASINH command
 * ##################################################################### */

function calcAsinh()
{
  if (CalcStack.length>0) { Output(Asinh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Asinh(z)
{
  return Ln(Add(z,Sqrt(Incr(Square(z)))));	
}

/* #####################################################################
 * ACOSH command
 * ##################################################################### */

function calcAcosh()
{
  if (CalcStack.length>0) { Output(Acosh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Acosh(z)
{
  return Ln(Add(z,Mult(Sqrt(Decr(z)),Sqrt(Incr(z)))));	
}

/* #####################################################################
 * ATANH command
 * ##################################################################### */

function calcAtanh()
{
  if (CalcStack.length>0) { Output(Atanh(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Atanh(z)
{
  var z1 = new cplx(1,0);
  return Ln(Div(Sqrt(Sub(z1,Square(z))),Sub(z1,z)));	
}

/* #####################################################################
 * ACSCH command
 * ##################################################################### */

function calcAcsch()
{
  if (CalcStack.length>0) { Output(Acsch(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Acsch(z)
{
  return Ln(Add(Sqrt(Incr(Inv(Square(z)))),Inv(z)));
}

/* #####################################################################
 * ASECH command
 * ##################################################################### */

function calcAsech()
{
  if (CalcStack.length>0) { Output(Asech(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Asech(z)
{
  return Ln(Add(Mult(Sqrt(Decr(Inv(z))),Sqrt(Incr(Inv(z)))),Inv(z)));
}

/* #####################################################################
 * ACOTH command
 * ##################################################################### */

function calcAcoth()
{
  if (CalcStack.length>0) { Output(Acoth(CalcStack[CalcStack.length-1]),1); }
  return null;
}

function Acoth(z)
{
  var z2 = new cplx(2,0);
  return Div(Ln(Div(Incr(z),Decr(z))),z2);
}

/* #####################################################################
 * PI command
 * ##################################################################### */

function calcPi()
{
  CalcStack.push(Pi());
  closeStack();
  showDisplay(0);

  return null;
}

function Pi()
{
  return new cplx(Math.PI,0);
}

/* #####################################################################
 * Imaginary unit command
 * ##################################################################### */

function calci()
{
  var out = new cplx(0,1);
 
  CalcStack.push(out);
  closeStack();
  showDisplay(0);
  
  return null;
}

/* #####################################################################
 * Infinity command
 * ##################################################################### */

function calcInfty()
{
  var w = new cplx(1/0,0);	
  CalcStack.push(w);
  closeStack();
  showDisplay(0);
  return null;
}

/* #####################################################################
 * Random number
 * ##################################################################### */

function calcRandom()
{
  var z = new cplx(Math.random(),0);	
  CalcStack.push(z);
  closeStack();
  showDisplay(0);
  return null;
}

/* #####################################################################
 * Color theme selector tool
 * ##################################################################### */

function showThemes()
{
  // local variables
  var txt="";
  var apx='"';
  
  txt += "<br>.<a onclick=" + apx + "setActiveStyleSheet('night',1);       return false;" + apx + " href=" + apx + "#" + apx + ">Night      </a><i id=" + apx + "scheme0" + apx + "></i>";
  txt += "<br>.<a onclick=" + apx + "setActiveStyleSheet('tarheels',1);    return false;" + apx + " href=" + apx + "#" + apx + ">Tar Heels  </a><i id=" + apx + "scheme1" + apx + "></i>";
  txt += "<br>.<a onclick=" + apx + "setActiveStyleSheet('sea',1);         return false;" + apx + " href=" + apx + "#" + apx + ">Deep Sea   </a><i id=" + apx + "scheme2" + apx + "></i>";
  txt += "<br>.<a onclick=" + apx + "setActiveStyleSheet('coffee',1);      return false;" + apx + " href=" + apx + "#" + apx + ">Coffee     </a><i id=" + apx + "scheme3" + apx + "></i>";
  txt += "<br>.<a onclick=" + apx + "setActiveStyleSheet('petroleum',1);   return false;" + apx + " href=" + apx + "#" + apx + ">Petroleum  </a><i id=" + apx + "scheme4" + apx + "></i>";
    
  // html line to select a different tool
  txt += toolSelector("ActiveTool='Calculator'; showCalculator();","Themes","ActiveTool='Clock'; showTool();") + "<br>";
  
  // html update
  document.getElementById("toolbox").innerHTML=txt;

  return null;
}

/* #####################################################################
 * html line to select a new tool
 * ##################################################################### */

function toolSelector(prev,current,next)
{
  // local variables
  var txt="<br>";
  var apx='"';

  txt += "<a onclick=" + apx + prev + apx + " href=" + apx + "#" + apx + "><b>&lt;&lt;</b></a>";
  txt += "&nbsp;&nbsp; " + current + " &nbsp;&nbsp;";
  txt += "<a onclick=" + apx + next + apx + " href=" + apx + "#" + apx + "><b>&gt;&gt;</b></a><br>";
  
  return txt;
}

/* #####################################################################
 * Activates a specified color theme (css style sheet), w or w/o
 * signaling (signal = 0 or 1) in the html page
 * ##################################################################### */

function setActiveStyleSheet(title,signal) 
{
  // local variables
  var i, a, main;

  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) 
  {
    if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) 
    {
      a.disabled = true;
      if(a.getAttribute("title") == title) 
      {
        a.disabled = false;
        if(signal==1) document.getElementById("scheme"+i).innerHTML=" &lt;";
      }
      else
      {
        if(signal==1) document.getElementById("scheme"+i).innerHTML="";
      }
    }
  }

  return null;
}

/* #####################################################################
 * Detects the active color theme (css style sheet)
 * ##################################################################### */

function getActiveStyleSheet() 
{
  // local variables
  var i, a;

  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) 
  {
    if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled) return a.getAttribute("title");
  }
  return null;
}

/* #####################################################################
 * Randomizer, to propose a color theme (css style sheet)
 * ##################################################################### */

function randomStyleSheet()
{
  // local variables
  var i;

  // random number (from the uniform ditribution)
  i=Math.floor(Math.random()*5);
  
  // first apply default choice
  setActiveStyleSheet('night',0);
  
  // then possibly modify it
  if(i==1) setActiveStyleSheet('tarheels',0);
  if(i==2) setActiveStyleSheet('sea',0);
  if(i==3) setActiveStyleSheet('coffee',0);
  if(i==4) setActiveStyleSheet('petroleum',0);
  
  // watch out: setActiveStyleSheet is called with no signaling (0) 
  // because when the randomizer is called the recipient html portion 
  // is not yet existing

  return null;
}
