/////////////////////////////////////////////////////////////////////
//
// Aritm by M.O.B. as Java ME MIDlet app for mobile phones.
// Copyright (C) 1992-2007 by Mikael O. Bonnier, Lund, Sweden.
// License: GNU GPL v3 or later, http://www.gnu.org/licenses/gpl-3.0.txt
// Donations are welcome to PayPal mikaelb@df.lth.se.
// The source code is at <http://www.df.lth.se/~mikaelb/wap/>.
//
// It was developed in J2ME (Java) using WTK-2.2 [patch 200511] and JDK 1.5.0_09 on
// Windows 2000. The jar-file was generated using the default obfuscator ProGuard 3.7
// using default settings. This file does also compile using WTK-2.5.2 and JDK 1.6.0_06 on
// Kubuntu Hardy Heron.
//
// Revision history:
// 1992:     MS-DOS version in C.
// 1997-Feb: 1.0bX  Beta versions as Java SE applet and application.
// 2007:     0.5 for mobile phones with Java ME.
//
// Suggestions, improvements, and bug-reports
// are always welcome to:
//                  Mikael Bonnier
//                  Osten Undens gata 88
//                  SE-227 62  LUND
//                  SWEDEN
//
// Or use my electronic addresses:
//     Web: http://www.df.lth.se/~mikaelb/
//     E-mail/MSN: mikaelb@df.lth.se
//     ICQ # 114635318
//     Skype: mikael4u
//              _____
//             /   / \
// ***********/   /   \***********
//           /   /     \
// *********/   /       \*********
//         /   /   / \   \
// *******/   /   /   \   \*******
//       /   /   / \   \   \
// *****/   /   /***\   \   \*****
//     /   /__ /_____\   \   \
// ***/               \   \   \***
//   /_________________\   \   \
// **\                      \  /**
//    \______________________\/
//
// Mikael O. Bonnier
/////////////////////////////////////////////////////////////////////

import java.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import java.util.Random;

public class Aritm extends MIDlet implements ItemStateListener, CommandListener {
   private final String _sHelpText = 
"Aritm trains you in simple mental calculation. This program teaches"
+ " its users the addition, subtraction, multiplication, and division"
+ " tables. It employs a very effective method, which makes the process"
+ " short with these useful tables. If you know these tables it is much"
+ " easier to follow the mathematics education. Further more, you can use"
+ " them in everyday situations, especially if you also know rough"
+ " estimate calculation. These tables, neither more nor less, is"
+ " everything you need to know by heart, because there are manual"
+ " methods for calculating with more complicated numbers. It is always"
+ " good to do an Aritm workout before you are going to a math test."
// + " Also, you can use Aritm to train calculation in foreign languages."
+ " You can mix the problems anyway you like, e.g. you can choose"
+ " addition and multiplication at the same time.\n\n"

+ "A d d i t i o n\n"
+ "(1) The sum of two single digit terms.\n"
+ "(2) The sum of one double digit and one single digit term.\n\n"
 
+ "S u b t r a c t i o n\n"
+ "(1) The difference of two small terms.\n" 
+ "(2) The difference of one larger term and one smaller.\n"
+ "(From) The problem is written M from N, instead of N-M.\n\n"

+ "M u l t i p l i c a t i o n\n"
+ "The product of two single digit factors.\n\n"

+ "D i v i s i o n\n"
+ "The quotient between two numbers. The dividend (numerator)"
+ " is usually larger than the divisor (denominator).\n\n"

+ "S t a r t / S t o p\n"
+ "When you have checked which problems you want, you select START,"
+ " and write the answer using digits and select ENTER. If your answer"
+ " is incorrect, you will get that problem again, later. If you want"
+ " to end prematurely, select STOP. You should make about 10 problems"
+ " per minute or more. "
+ " You may pause and the clock is stopped during the pause.\n\n"

+ "Aritm is also available as a Java applet with more features at"
+ " <http://www.df.lth.se/~mikaelb/aritm/aritm.shtml>. Welcome!" ;
   private List _lstMenu;
   private Form _frmProblem;
   private TextField _tfProblem;
   private StringItem _siResult;
   private Form _frmProblemsChoice;
   private ChoiceGroup _chgAdd, _chgSub, _chgChoice, _chgDiv;
   private boolean _bAddSave, _bSubSave, _bMulSave, _bDivSave;
   private ChoiceGroup _chgAddLev, _chgSubLev;
   private boolean _bAddLevSave[] = new boolean[2], _bSubLevSave[] = new boolean[3];
   private StringItem _siNoOfProblems;
   private Command _cmdExit, _cmdSelect, _cmdMenu, _cmdEnter, _cmdStop, _cmdStart,
      _cmdCancel, _cmdOk;

   private String _sLanguage = "enu";
   private String _sGood;
   private String _sProbPerMin;
   private String _sNotFin;
   private String _sProblems;
   private String _sAdd;
   private String _sSub;
   private String _sFrom;
   private String _sMul;
   private String _sDiv;
   private String _sArabic;
   private String _sWords;
   private String _sRoman;
   private String _sStart;
   private String _sStop;
   private String _sTryAgain;
   private String _sRight;
   private String _sProbsLeft;
   private String _sWrong;
   private String _sIs;
   private String _sIntPart;
   private String _sDividedBy;
   private String _sSFrom;
   private String _sPlus;
   private String _sMinus;
   private String _sTimes;
   private String _sFromSuffix;
   private char MULSIGN;
   private char DIVSIGN;

   private final int MAXPROB = 590;
   private Problem _prob[] = new Problem[MAXPROB];
   private int _index[] = new int[MAXPROB];
   private int _nUsedIndices;
   private int _iCurrent;
   private int _iIndex;
   private boolean _bRunning = false;
   private String _sProblem;
   private Random _rand = new Random();
   private int _total;  // Total number of Problems asked so far.
   private long _lStart;
   private long _lPause;
   private final int REQD = 1; // Number of times a Problem must be answered correctly
                               // before it is removed from active part of _prob[].

  
   public Aritm() {
      _sGood = "Good!!!   You made ";
      _sProbPerMin = " problems/minute.";
      _sNotFin = "Not finished.   You made ";
      _sProblems = " problems.";
      _sAdd = "Addition";
      _sSub = "Subtraction";
      _sFrom = "From";
      _sMul = "Multiplication";
      _sDiv = "Division";
      _sTryAgain = "Try again.";
      _sRight = "Right! ";
      _sProbsLeft = " problems left.";
      _sWrong = "Wrong. ";
      _sIs = " is ";
      _sIntPart = "The integer part of ";
      _sSFrom = " from ";
      _sFromSuffix = "";
      MULSIGN = '×';
      DIVSIGN = '÷';

      String[] stringElements = {"START", "SETUP", "HELP", "ABOUT"};
      String sFilename = "/Aritm.png";
      Image[] imageElements = {loadImage(sFilename), loadImage(sFilename),
         loadImage(sFilename), loadImage(sFilename)};
      _lstMenu = new List("Aritm", List.IMPLICIT,
        stringElements, imageElements);    
      _cmdSelect = new Command("SELECT", Command.OK, 0);
      _cmdExit = new Command("EXIT", Command.EXIT, 0);
      _lstMenu.addCommand(_cmdSelect);
      _lstMenu.addCommand(_cmdExit);
      _lstMenu.setCommandListener(this);

      _frmProblem = new Form("Aritm/Problem");
      _cmdMenu = new Command("MENU", Command.BACK, 2);
      _cmdEnter = new Command("ENTER", Command.OK, 0);
      _cmdStop = new Command("STOP", Command.SCREEN, 1);
      _cmdStart = new Command("START", Command.SCREEN, 1);
      _frmProblem.addCommand(_cmdMenu);
      _frmProblem.setCommandListener(this);
      _tfProblem = new TextField("", "", 3, TextField.NUMERIC);
      _frmProblem.append(_tfProblem);
      _siResult = new StringItem(null, "");
      _frmProblem.append(_siResult);

      _frmProblemsChoice = new Form("Aritm/Setup");
      _cmdCancel = new Command("CANCEL", Command.CANCEL, 0);
      _cmdOk = new Command("OK", Command.OK, 0);
      _frmProblemsChoice.addCommand(_cmdCancel);
      _frmProblemsChoice.addCommand(_cmdOk);
      _frmProblemsChoice.setCommandListener(this);
      _chgAdd = new ChoiceGroup(null, Choice.MULTIPLE);
      _chgAdd.append(_sAdd, null);
      _chgAdd.setSelectedIndex(0, true);
      _frmProblemsChoice.append(_chgAdd);
      _chgAddLev = new ChoiceGroup(null, Choice.MULTIPLE);
      String sSpacer = "    ";
      _chgAddLev.append(sSpacer + "1", null);
      _chgAddLev.append(sSpacer + "2", null);
//      _chgAddLev.setLayout(Item.LAYOUT_2 | Item.LAYOUT_LEFT | Item.LAYOUT_SHRINK);
      _chgAddLev.setSelectedIndex(0, true);
      _frmProblemsChoice.append(_chgAddLev);
      _chgSub = new ChoiceGroup(null, Choice.MULTIPLE);
      _chgSub.append(_sSub, null);
      _frmProblemsChoice.append(_chgSub);
      _chgSubLev = new ChoiceGroup(null, Choice.MULTIPLE);
      _chgSubLev.append(sSpacer + "1", null);
      _chgSubLev.append(sSpacer + "2", null);
      _chgSubLev.append(sSpacer + _sFrom, null);
//      _chgSubLev.setLayout(Item.LAYOUT_2 | Item.LAYOUT_LEFT | Item.LAYOUT_SHRINK);
      _chgSubLev.setSelectedIndex(0, true);
      _frmProblemsChoice.append(_chgSubLev);
      _chgChoice = new ChoiceGroup(null, Choice.MULTIPLE);
      _chgChoice.append(_sMul, null);
      _frmProblemsChoice.append(_chgChoice);
      _chgDiv = new ChoiceGroup(null, Choice.MULTIPLE);
      _chgDiv.append(_sDiv, null);
      _frmProblemsChoice.append(_chgDiv);
      _siNoOfProblems = new StringItem(null, "");
      _frmProblemsChoice.append(_siNoOfProblems);
      _frmProblemsChoice.setItemStateListener(this);
      itemStateChanged(_chgAdd);
   }
  
   public void startApp() {
      if(_bRunning) {
         _lStart += System.currentTimeMillis() - _lPause;
         Display.getDisplay(this).setCurrent(_frmProblem);
      }
      else 
         Display.getDisplay(this).setCurrent(_lstMenu);
   }

   public void pauseApp() 
   {
      if(_bRunning)
         _lPause = System.currentTimeMillis();
      else {
         startCmds();
      }
   }

   public void destroyApp(boolean unconditional) {}
  
   public void commandAction(Command c, Displayable s) 
   {
      if(c == _cmdSelect || c == List.SELECT_COMMAND) {
         int i = _lstMenu.getSelectedIndex();
         switch(i)
         {
            case 0: // START
               Display.getDisplay(this).setCurrent(_frmProblem);
               start();
               break;
            case 1: // SETUP 
               _bAddSave = _chgAdd.isSelected(0);
               _bSubSave = _chgSub.isSelected(0);
               _bMulSave = _chgChoice.isSelected(0);
               _bDivSave = _chgDiv.isSelected(0);
               _chgAddLev.getSelectedFlags(_bAddLevSave);
               _chgSubLev.getSelectedFlags(_bSubLevSave);
               Display.getDisplay(this).setCurrent(_frmProblemsChoice);
               break;
            case 2: // HELP
               Alert alrHelp = new Alert("Aritm/Help", _sHelpText, null, AlertType.INFO);
               alrHelp.setTimeout(Alert.FOREVER);
               Display.getDisplay(this).setCurrent(alrHelp, _lstMenu);
               break;
            case 3: // ABOUT
               Runtime rt = Runtime.getRuntime();
               Alert alrAbout = new Alert("About Aritm v." + getAppProperty("MIDlet-Version"), 
                  "This program \"Aritm\" is Copyright © 1997-2007 by Mikael O. Bonnier, Lund, Sweden"
                  +" <mikaelb@df.lth.se>."
                  + " All rights reserved.\n"
                  + "DISCLAIMER: THIS PROGRAM IS USED AT YOUR OWN RISK.\n"
                  + "Free mem: " + rt.freeMemory() + " B\n"
                  + "Total mem: " + rt.totalMemory() + " B\n"
                  + "Profiles: " + System.getProperty("microedition.profiles") + '\n'
                  + "Config: " + System.getProperty("microedition.configuration") + '\n'
                  + "Encoding: " + System.getProperty("microedition.encoding") + '\n'
                  + "Locale: " + System.getProperty("microedition.locale") + '\n',
                  null, AlertType.INFO);
               alrAbout.setTimeout(Alert.FOREVER);
               Display.getDisplay(this).setCurrent(alrAbout, _lstMenu);
               break;
            default:
               Alert alert = new Alert("Your selection",
                  "You chose " + _lstMenu.getString(i) + ".",
                  null, AlertType.INFO);
               Display.getDisplay(this).setCurrent(alert, _lstMenu);
         }
      }
      else if(c == _cmdOk) 
         Display.getDisplay(this).setCurrent(_lstMenu);
      else if(c == _cmdCancel) {
         _chgAdd.setSelectedIndex(0, _bAddSave);
         _chgSub.setSelectedIndex(0, _bSubSave);
         _chgChoice.setSelectedIndex(0, _bMulSave);
         _chgDiv.setSelectedIndex(0, _bDivSave);
         _chgAddLev.setSelectedFlags(_bAddLevSave);
         _chgSubLev.setSelectedFlags(_bSubLevSave);
         Display.getDisplay(this).setCurrent(_lstMenu);     
      }
      else if(c == _cmdMenu) {
         startCmds();
         Display.getDisplay(this).setCurrent(_lstMenu);     
      }
      else if(c == _cmdExit)
         notifyDestroyed();
      else if(c == _cmdEnter && _bRunning)
         if(!check())
            stop();
         else
            ask();
      else if(c == _cmdStop) 
         stop();
      else if(c == _cmdStart) 
         start();
      else
         debug(c);
   }

   void start() 
   {
      generate();
      _total = 0;
      _siResult.setText(_nUsedIndices + _sProblems);
      ask();
      _lStart = System.currentTimeMillis();
      _bRunning = true;
      startCmds();
   }

   void startCmds() {
      _frmProblem.removeCommand(_cmdStart);
      _frmProblem.addCommand(_cmdStop);
      _frmProblem.addCommand(_cmdEnter);
   }

   void stop() 
   {
      displayScore();
      _bRunning = false;
      _frmProblem.removeCommand(_cmdEnter);
      _frmProblem.removeCommand(_cmdStop);
      _frmProblem.addCommand(_cmdStart);
   }

   public void itemStateChanged(Item item)
   {
      _siNoOfProblems.setText(String.valueOf(countProblems()) + _sProblems);
      debug("itemStateChanged" + item);
   }

   boolean debug(Object o) {
//      System.out.println("Debug!" + o);
      return true;
   }

   void displayScore()
   {
      long lStop = System.currentTimeMillis();
      if(_nUsedIndices == 0)
      {
//         bell(880); bell(1108.73); bell(1318.51); bell(1760);
         _siResult.setText(_sGood + (_total*60000)/(lStop-_lStart) + _sProbPerMin);
      }
      else
         _siResult.setText(_sNotFin + (_total*60000)/(lStop-_lStart) + _sProbPerMin);
   }

   private Image loadImage(String name) 
   {
      Image image = null;
      try {
         image = Image.createImage(name);
      }
      catch (IOException ioe) {
//         System.out.println(ioe);
      }
    
      return image;
   }

   int countProblems()
   {
      int nProblems = 0;
      if(_chgAdd.isSelected(0))
      {
         if(_chgAddLev.isSelected(0))
            nProblems += 100;
         if(_chgAddLev.isSelected(1))
            nProblems += 100;
      }
      if(_chgSub.isSelected(0))
      {
         if(_chgSubLev.isSelected(0))
            nProblems += 100;
         if(_chgSubLev.isSelected(1))
            nProblems += 100;
      }
      if(_chgChoice.isSelected(0))
         nProblems += 100;
      if(_chgDiv.isSelected(0))
         nProblems += 90;
      return nProblems;
   }

   void generate()
   {
      _nUsedIndices = _iIndex = 0;
      if(_chgAdd.isSelected(0))
      {
         if(_chgAddLev.isSelected(0))
            genAdd(0);
         if(_chgAddLev.isSelected(1))
            genAdd(1);
      }
      if(_chgSub.isSelected(0))
      {
         if(_chgSubLev.isSelected(0))
            if(!_chgSubLev.isSelected(2))
               genSub(0);
            else
               genSub(1);
         if(_chgSubLev.isSelected(1))
            if(!_chgSubLev.isSelected(2))
               genSub(2);
            else
               genSub(3);
      }
      if(_chgChoice.isSelected(0))
         genMul();
      if(_chgDiv.isSelected(0))
         genDiv();
      for(int i = 0; i < _nUsedIndices; ++i)
         _index[i] = i;
      shuffle();
   }

   void genAdd(int mod)
   {
      int i, j;

      for(i=0; i<=9; ++i)
         for(j=0; j<=9; ++j) {
            _prob[_nUsedIndices] = new Problem();
            _prob[_nUsedIndices].op1 = (mod==1 ? 10*((_rand.nextInt() & Integer.MAX_VALUE)%8+1) : 0)
               + i;
            _prob[_nUsedIndices].sign = '+';
            _prob[_nUsedIndices].op2 = j;
            _prob[_nUsedIndices].ans = _prob[_nUsedIndices].op1+j;
            _nUsedIndices++;
         }
   }

   void genSub(int mod)
   {
      int i, j;

      for(i=0; i<=9; ++i)
         for(j=i; j<=9+i; ++j) 
         {
            _prob[_nUsedIndices] = new Problem();
            _prob[_nUsedIndices].op1 = (mod==2 || mod==3 ? 10*((_rand.nextInt() 
               & Integer.MAX_VALUE)%9+1) : 0)+j;
            _prob[_nUsedIndices].sign  =  mod==1 || mod==3 ? 'f' : '-';
            _prob[_nUsedIndices].op2 = i;
            _prob[_nUsedIndices].ans = _prob[_nUsedIndices].op1-i;
            _nUsedIndices++;
         }
   }

   void genMul()
   {
      int i, j;

      for(i=0; i<=9; ++i)
         for(j=0; j<=9; ++j) 
         {
            _prob[_nUsedIndices] = new Problem();
            _prob[_nUsedIndices].op1 = i;
            _prob[_nUsedIndices].sign = MULSIGN;
            _prob[_nUsedIndices].op2 = j;
            _prob[_nUsedIndices].ans = i*j;
            _prob[_nUsedIndices].correct = 0;
            _nUsedIndices++;
         }
   }

   void genDiv()
   {
      int q, d;

      for(q=0; q<=9; ++q)
         for(d=1; d<=9; ++d) 
         {
            _prob[_nUsedIndices] = new Problem();
            _prob[_nUsedIndices].op1 = q*d+(_rand.nextInt() & Integer.MAX_VALUE)%d;
            _prob[_nUsedIndices].sign = DIVSIGN;
            _prob[_nUsedIndices].op2 = d;
            _prob[_nUsedIndices].ans = q;
            _nUsedIndices++;
         }
   }

   void ask()
   {
      _iCurrent = _index[_iIndex];
      _sProblem = expandProblem(_iCurrent);
      _tfProblem.setLabel(_sProblem);    
      _tfProblem.setString(""); 
      Display.getDisplay(this).setCurrentItem((Item)_tfProblem);
   }

   boolean check()
   {
      int nAns;
      String sAns = _tfProblem.getString();

      if(sAns.length() == 0)
      {
//         bell(ATTNFQ);
         _siResult.setText(_sTryAgain);
         return true;
      }
      ++_total;
      nAns = Integer.parseInt(sAns);
      if(nAns == _prob[_iCurrent].ans)
      {
         ++_prob[_iCurrent].correct;
         int nLeft = 0;
         for(int i = 0; i < _nUsedIndices; ++i) {
            nLeft = nLeft + REQD - _prob[_index[i]].correct;
         }
         _siResult.setText(_sRight + nLeft + _sProbsLeft);
         ++_iIndex;
      }
      else 
      {
         --_prob[_iCurrent].correct;
//         bell(ATTNFQ);
         _siResult.setText(_sWrong + _sProblem.substring(0, _sProblem.length() - 1) + _sIs +
            _prob[_iCurrent].ans + ".");
         packArray();
         shuffle();      
         _iIndex = 0;
      }
      if(_iIndex >= _nUsedIndices)
      {
         _iIndex = 0;
         return packArray();
      }
      return true;
   }

   boolean packArray()
   {
      int i, j, n = 0;

      for(i = 0; i < _nUsedIndices; ++i) 
      {
         j = _index[i];
         if(_prob[j].correct < REQD)
            _index[n++] = j;
      }
      _nUsedIndices = n;
      return _nUsedIndices != 0;
   }

   void shuffle()
   {
      int i, j, temp;

      for(i = 0; i < _nUsedIndices; ++i) 
      {
         j = (_rand.nextInt() & Integer.MAX_VALUE) % _nUsedIndices;
         temp = _index[i];
         _index[i] = _index[j];
         _index[j] = temp;
      }
   }

   String expandProblem(int indx)
   {
      StringBuffer sRet;

      switch(_prob[indx].sign) 
      {
         case '/': 
         case '÷':
            sRet = new StringBuffer(_sIntPart + _prob[indx].op1 + _prob[indx].sign + 
               _prob[indx].op2 + "=");
            break;
         case 'f':
            sRet = new StringBuffer(String.valueOf(_prob[indx].op2) + _sSFrom + _prob[indx].op1 +
               _sFromSuffix + "=");
            break;
         default:
            sRet = new StringBuffer(String.valueOf(_prob[indx].op1) + _prob[indx].sign + 
               _prob[indx].op2 + "=");
      } // switch   
      return sRet.toString();
   }
}

class Problem
{
   protected int op1;
   protected char sign;
   protected int op2;
   protected int ans;
   protected int correct;
}

