public abstract class Level {

    private final static Map<Integer, Integer> nextlevelnum = new HashMap<>();
    private final int mRounds;
    private final int mLevel;
    private final int mSubjectkey;

    Level(int subjectkey, int rounds) {
        mSubjectkey = subjectkey;
        mRounds = rounds;
        synchronized (nextlevelnum) {
            Integer lnum = nextlevelnum.get(mSubjectkey);
            if (lnum == null) lnum = 0;
            lnum++;
            nextlevelnum.put(mSubjectkey, lnum);
            mLevel = lnum;
        }
    }

    public abstract String getDescription(Context context);

    public abstract String getShortDescription(Context context);

    public int getLevelNum() {
        return mLevel;
    }

    public int getRounds() {
        return mRounds;
    }
} public enum InputMode {
    None, Buttons, Input
} public enum SubjectGroup {
    Math("M", R.string.group_m, R.color.group_color_m),
    LanguageArts("LA", R.string.group_la, R.color.group_color_la),
    TimeMoney("TM", R.string.group_tm, R.color.group_color_tm),
    Science("Sc", R.string.group_sc, R.color.group_color_sc),
    Music("Mu", R.string.group_mu, R.color.group_color_mu),
    Art("Ar", R.string.group_ar, R.color.group_color_ar);


    private final String mCode;
    private final int mResId;
    private final int mColorResId;

    SubjectGroup(String code, int resId, int colorResId) {
        mCode = code;
        mResId = resId;
        mColorResId = colorResId;
    }

    public String getCode() {
        return mCode;
    }

    public int getColor(Context context) {
        if (Build.VERSION.SDK_INT >= 23) {
            return context.getColor(mColorResId);
        } else {
            return context.getResources().getColor(mColorResId);
        }
    }

    public String getText(Context context) {
        return context.getString(mResId);
    }


} public class Subjects {

    private static Subjects inst;
    private final Desc[] subjects;
    private final Map<String, Desc> subjectMap = new HashMap<>();
    private final List<String> codes = new ArrayList<>();
    private Subjects(Context context) {
        subjects = loadSubjects(context);
        for (Desc subject : subjects) {
            subjectMap.put(subject.getCode(), subject);
            codes.add(subject.getCode());
        }
    }

    public synchronized static Subjects getInstance(Context context) {
        if (inst == null) {
            inst = new Subjects(context);
        }
        return inst;
    }

    private static Desc[] loadSubjects(Context context) {

        return Levels.getSubjectInstances(context);
    }

    public List<String> getCodes() {

        return codes;
    }

    public String getNextCode(String code) {
        int pos = codes.indexOf(code);
        if (pos + 1 < codes.size()) {
            return codes.get(pos + 1);
        }
        return null;
    }


    public int getCount() {
        return subjects.length;
    }

    public Desc get(String code) {
        return subjectMap.get(code);
    }

    public Desc get(int num) {
        return subjects[num];
    }

    public static class Desc {

        private SubjectGroup group;
        private String code;
        private String name;
        private String desc;
        private Class activityclass;


        private final Level[] levels;

        public Desc(Context context, SubjectGroup group, int code, int name, int desc, Class activityClass, Level[] levels) {
            this.setGroup(group);
            this.setCode(context.getString(code));
            this.setName(context.getString(name));
            this.setDesc(context.getString(desc));
            this.setActivityclass(activityClass);
            this.levels = levels;

        }

        public String getCode() {
            return code;
        }

        void setCode(String code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        void setDesc(String desc) {
            this.desc = desc;
        }

        public Class getActivityclass() {
            return activityclass;
        }

        void setActivityclass(Class activityclass) {
            this.activityclass = activityclass;
        }

        public Level[] getLevels() {
            return levels;
        }

        public SubjectGroup getGroup() {
            return group;
        }

        void setGroup(SubjectGroup group) {
            this.group = group;
        }


    }
} public class BasicMathActivity extends StdGameActivity implements SubjectBaseActivity.AnswerGivenListener<Integer>, SubjectBaseActivity.AnswerTypedListener {

    private int num1;
    private int num2;
    private MathOp op;
    private int answer;

    public BasicMathActivity() {
        super(R.layout.std_math_prob);

    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onPause() {
        if (op!=null) {
            saveLevelValue("num1", num1);
            saveLevelValue("num2", num2);
            saveLevelValue("op", op.name());
        }
        super.onPause();
    }



    @Override
    protected void onShowProbImpl() {

        TextView txtMathHint = findViewById(R.id.txtMathHint);
        txtMathHint.setText("");

        TextView num1txt = findViewById(R.id.num1);
        TextView num2txt = findViewById(R.id.num2);
        TextView optxt = findViewById(R.id.op);

        num1 = getSavedLevelValue("num1", Integer.MIN_VALUE);
        num2 = getSavedLevelValue("num2", Integer.MIN_VALUE);
        op = MathOp.valueOf(getSavedLevelValue("op", "Plus"));
        if (num1 == Integer.MIN_VALUE || num2 == Integer.MIN_VALUE) {
            makeRandomProblem();
        } else {
            deleteSavedLevelValue("num1");
            deleteSavedLevelValue("num2");
            deleteSavedLevelValue("op");
        }

        num1txt.setText(String.format(Locale.getDefault(), "%d", num1));
        num2txt.setText(String.format(Locale.getDefault(), "%d", num2));
        optxt.setText(op.toString());
        answer = getAnswer(num1, num2, op);

        // LinearLayout answerarea = (LinearLayout)findViewById(R.id.answer_area);
        float fontsize = num1txt.getTextSize();
        BasicMathLevel level = (BasicMathLevel) getLevel();

        int fac = Math.max(3, (int)Math.sqrt(level.getMaxNum() + (op.ordinal()*2)));

        if (level.getInputMode() == InputMode.Buttons) {
            setFasttimes(fac * 300, fac * 400, fac * 600);
            makeAnswerButtons(getAnswerArea(), fontsize);
        } else if (level.getInputMode() == InputMode.Input) {
            setFasttimes(fac * 400, fac * 600, fac * 800);
            makeInputBox(getAnswerArea(), getKeysArea(), this, INPUTTYPE_NUMBER, 3, fontsize / 2);
            startHint(op.ordinal() + 1);

        } else {
            throw new IllegalArgumentException("Unknown inputMode! " + level.getInputMode());
        }


    }


    @Override
    protected void onPerformHint(int hintTick) {
        String a = answer+"";
        if (hintTick <a.length()){
            TextView txtMathHint = findViewById(R.id.txtMathHint);
            txtMathHint.setText(a.substring(0, hintTick+1));
        }
        super.onPerformHint(hintTick);
    }

    private int getAnswer(int n1, int n2, MathOp op) {
        switch (op) {
            case Plus:
                return n1 + n2;
            case Minus:
                return n1 - n2;
            case Times:
                return n1 * n2;
            case Divide:
                return n1 / n2;
            default:
                throw new IllegalArgumentException("Unknown operator: " + op);
        }
    }


    @Override
    public boolean onAnswerTyped(String answer) {
        int ans;
        try {
            ans = Integer.parseInt(answer);
        } catch (NumberFormatException ne) {
            return false;
        }
        return onAnswerGiven(ans);
    }


    @Override
    public boolean onAnswerGiven(Integer ans) {

        boolean isright = ans == answer;

        answerDone(isright, num1 + op.toString() + num2, answer + "", ans + "");

        return isright;

    }

    /**
     *  Calculate the points from the current problem
     *  By default this is based on simply the level number.
     *
     *  Override this in each activity.
     *
     *  Range, in general:
     *    difficulty 1:   1 - 50
     *    difficulty 2:   up to 100
     *    difficulty 3:   up to 200
     *    difficulty 4:   up to 500
     *    difficulty 5:   up to 1000
     *
     * @return the points for the current problem
     */
    @Override
    protected int onCalculatePoints() {
        float scoremult = (answer+"").length() - getHintTicks();

        if (scoremult<=0) {  //if the hint is fully shown, give partial credit.
            scoremult=.3f;
        }

        return super.onCalculatePoints() + (int)((1 + Math.abs(num1) + Math.abs(num2)) * (op.ordinal() + 1) * scoremult);
    }

    private void makeRandomProblem() {

        int tries = 0;
        BasicMathLevel level = (BasicMathLevel) getLevel();
        boolean negsallowed = level.getNegatives() != Negatives.None;
        do {
            int min = 0;
            int max = level.getMaxNum();
            if (negsallowed) {
                min = -max;
            }

            op = MathOp.random(level.getMinMathOp(), level.getMaxMathOp());

            if (correct > level.getRounds() / 2) { //increase difficulty in second half of level
                if (negsallowed) {
                    num1 = Math.random() > 5 ? getRand(max / 2 - 1, max) : getRand(min, min / 2 + 1);
                } else {
                    num1 = getRand(max / 2 - 1, max);
                }
            } else {
                num1 = getRand(min / 2, max / 2 + 1);
            }
            if (level.getDoubles()) {
                if (num1==0) {
                    num1 =  getRand(1, max);
                }
                num2 = num1;
            } else {
                num2 = getRand(min, max);
                if ((num2 == 0 || num2 == 1) && Math.random() > .3)
                    num2 = getRand(2, max); //reduce number of x+0 and x+1

                if (level.getNegatives() == Negatives.Required && num1 >= 0 && num2 >= 0) { //force a negative value
                    num1 = -getRand(1, max);
                }

            }
            if ((op == MathOp.Minus && level.getNegatives() == Negatives.None) || op == MathOp.Divide) {
                if (num1 < num2) {
                    int tmp = num1;
                    num1 = num2;
                    num2 = tmp;
                }
                if (op == MathOp.Divide) {
                    if (num2 == 0) {
                        num2 = getRand(1, max);
                        if (num1 < num2) num1 = getRand(num2, max);
                    }
                   // if (num1 % num2 != 0) {
                        num1 = num1 * num2;
                   // }
                }
            }
            if (op == MathOp.Minus) {
                num1 = num1 + num2;
            }
            //prevent 2 identical problems in a row
        } while (tries++ < 50 && seenProblem(num1, num2, op));


    }

    private List<Integer> getAnswerChoices(int numans) {
        List<Integer> answers = new ArrayList<>();
        answers.add(answer);

        boolean allownegs = ((BasicMathLevel) getLevel()).getNegatives() != Negatives.None;
        int range = Math.abs(answer / 10);
        for (int i = 1; i < numans; i++) {
            int tmpans;
            do {

                if (allownegs) {

                    if (getRand(10) > 8) {
                        tmpans = Math.abs(num1) + Math.abs(num2);
                    } else if (getRand(10) > 5) {
                        tmpans = -answer;
                    } else {
                        tmpans = answer + getRand(-3 - range, 3 + range);
                    }

                } else if (getRand(10) > 3) {
                    tmpans = answer + getRand(-3 - range, 3 + range); //normal random
                } else {
                    //tricky answers
                    switch (op) {
                        case Plus:
                            tmpans = Math.max(num1, num2) - Math.min(num1, num2);
                            break;
                        case Times:
                            tmpans = num1 * (num2 + getRand(1, 2));
                            break;
                        case Divide:
                            tmpans = num1 - num2;
                            break;
                        case Minus:
                        default:
                            tmpans = num1 + num2;
                            break;
                    }
                }

            } while (answers.contains(tmpans) || (!allownegs && tmpans < 0));
            answers.add(tmpans);
        }
        Collections.shuffle(answers);
        return answers;
    }


    @SuppressLint("RtlHardcoded")
    private void makeAnswerButtons(GridLayout answerarea, float fontsize) {

        List<Integer> answers = getAnswerChoices(4);



        makeChoiceButtons(answerarea, answers, this, fontsize, null, Gravity.RIGHT);

    }


} public class BasicMathLevel extends StdLevel {

    private final MathOp mMaxMathOp;
    private final MathOp mMinMathOp;
    private final int mMaxNum;
    private final Negatives mNegatives;
    private final boolean mDoubles;

    public BasicMathLevel(int subjectkey, MathOp maxMathOp, int maxNum, int rounds, InputMode inputMode) {
        this(subjectkey, maxMathOp, maxMathOp, maxNum, rounds, inputMode);
    }

    public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, int rounds, InputMode inputMode) {
        this(subjectkey, maxMathOp, minMathOp, maxNum, Negatives.None, rounds, inputMode);
    }

    public BasicMathLevel(int subjectkey, MathOp maxMathOp, int maxNum, Negatives negatives, int rounds, InputMode inputMode) {
        this(subjectkey, maxMathOp, maxMathOp, maxNum, negatives, rounds, inputMode);
    }
    public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, Negatives negatives, int rounds, InputMode inputMode) {
        this(subjectkey, maxMathOp, minMathOp, maxNum, negatives, rounds, false, inputMode);
    }
    public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, Negatives negatives, int rounds, boolean doubles, InputMode inputMode) {
        super(subjectkey, rounds, inputMode);

        mMaxMathOp = maxMathOp;
        mMinMathOp = minMathOp;
        mMaxNum = maxNum;
        mNegatives = negatives;
        mDoubles = doubles;

    }

    @Override
    public String getDescription(Context context) {
        String ops = getOpsStr(context);
        String max = (mNegatives != Negatives.None ? "\u00B1" : "") + mMaxNum;
        return ops + " / " +(getDoubles()?context.getString(R.string.doubles):"")+ context.getString(R.string.max, max) + ". " + getInputModeString(context);

    }

    @Override
    public String getShortDescription(Context context) {
        String ops = getOpsSymStr();
        if (mMaxMathOp == mMinMathOp) {
            ops = mMaxMathOp.name();
        }
        String max = (mNegatives != Negatives.None ? "\u00B1" : "") + mMaxNum;
        return context.getString(R.string.max, max) + ". " + ops;
    }

    private String getOpsStr(Context context) {
        String ops = "";
        for (MathOp m : MathOp.values()) {
            if (m.ordinal() >= mMinMathOp.ordinal() && m.ordinal() <= mMaxMathOp.ordinal()) {
                ops += context.getString(m.getResId());
                if (m.ordinal() < mMaxMathOp.ordinal()) {
                    ops += ", ";
                }
            }
        }
        return ops;
    }

    private String getOpsSymStr() {
        String ops = "";
        for (MathOp m : MathOp.values()) {
            if (m.ordinal() >= mMinMathOp.ordinal() && m.ordinal() <= mMaxMathOp.ordinal()) {
                ops += m.toString();
                if (m.ordinal() < mMaxMathOp.ordinal()) {
                    ops += ", ";
                }
            }
        }
        return ops;
    }

    public MathOp getMaxMathOp() {
        return mMaxMathOp;
    }

    public MathOp getMinMathOp() {
        return mMinMathOp;
    }

    public int getMaxNum() {
        return mMaxNum;
    }

    public Negatives getNegatives() {
        return mNegatives;
    }

    public boolean getDoubles() {
        return mDoubles;
    }


//    public int getMaxNum(int prevCorrect) {
//        return (int)(mMaxNum * ((double)Math.max(prevCorrect, mRounds/5.0)/mRounds));
//    }

} public enum MathOp {
    Plus("+", R.string.plus),
    Minus("-", R.string.minus),
    Times("\u00D7", R.string.times),
    Divide("\u00F7", R.string.divide);

    private static final SecureRandom random = new SecureRandom();
    private final String mDisplay;
    private final int mResid;

    MathOp(String display, int resid) {
        mDisplay = display;
        mResid = resid;
    }

    public static MathOp random(MathOp upto) {
        return randomEnum(MathOp.class, MathOp.Plus, upto);
    }

    public static MathOp random(MathOp start, MathOp upto) {
        return randomEnum(MathOp.class, start, upto);
    }

    private static <T extends Enum<?>> T randomEnum(Class<T> clazz, T start, T upto) {

        int max = upto.ordinal();
        int min = start.ordinal();

        int x = random.nextInt(max - min + 1) + min;
        return clazz.getEnumConstants()[x];
    }

    @Override
    public String toString() {
        return mDisplay;
    }

    public int getResId() {
        return mResid;
    }
} public enum Negatives {
    None, Allowed, Required
} public class SortingActivity extends StdGameActivity implements
        View.OnTouchListener, View.OnDragListener {

    private static final int BACKGROUND_COLOR = Color.rgb(200, 200, 200);

    private List<Integer> numlist;
    private boolean problemDone = false;
    private int moves = 0;
    private int numcolumns = 3;

    public SortingActivity() {
        super(R.layout.std_sorting_prob);
        setUseInARow(false);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (numlist!=null) {
            saveLevelValue("numlist", join(",", numlist));
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        GridLayout sortArea = findViewById(R.id.sort_area);
        if (isLandscape()) {
            numcolumns = 6;
        } else {
            numcolumns = 3;
        }
        sortArea.setColumnCount(numcolumns);

        //override super class
        LinearLayout centercol = findViewById(R.id.centercol);
        centercol.setOrientation(LinearLayout.VERTICAL);


        findViewById(R.id.score_total_correct_area).setVisibility(View.GONE);

        findViewById(R.id.score_level_percent_area).setVisibility(View.GONE);
    }

    @Override
    protected void onShowProbImpl() {
        GridLayout sortArea = findViewById(R.id.sort_area);
        sortArea.removeAllViews();

        sortArea.setOnDragListener(this);

        Set<Integer> nums = new TreeSet<>();
        SortingLevel level = ((SortingLevel) getLevel());

        String numliststr = getSavedLevelValue("numlist", (String) null);
        if (numliststr == null || numliststr.length() == 0) {
            int tries = 0;
            do {
                nums.add(getRand(level.getMaxNum()));
            } while (tries++ < 100 && nums.size() < level.getNumItems());

            numlist = new ArrayList<>(nums);

            do {
                Collections.shuffle(numlist);
            } while (isSorted(numlist));

        } else {
            numlist = splitInts(",", numliststr);
            deleteSavedLevelValue("numlist");
        }

        int mlen = (level.getMaxNum() + "").length();
//        int xsize = getScreenSize().x;
//
//        int tsize = xsize*2/3 / numcolumns / 4 - 5;
//        if (tsize>30) tsize = 30;
        int tsize = 30 - mlen;

        for (int num : numlist) {
            String spaces = "";
            for (int i = 0; i < Math.max(3, mlen) - (num + "").length(); i++) {
                spaces += " ";
            }

            final String numstr = spaces + num;

            TextView item = new TextView(this);
            item.setText(numstr);
            item.setTag(num);
            item.setOnTouchListener(this);
            item.setOnDragListener(this);


            item.setMaxLines(1);
            item.setTextSize(tsize);
            item.setTypeface(Typeface.MONOSPACE);
            //item.setWidth(xsize*2/3 / numcolumns -10);
            //item.setEms(mlen);

            item.setGravity(Gravity.END);

            GridLayout.LayoutParams lp = new GridLayout.LayoutParams();

            lp.setMargins(1, 1, 1, 1);
            lp.setGravity(Gravity.CENTER);
            item.setLayoutParams(lp);
            item.setBackgroundColor(BACKGROUND_COLOR);
            item.setPadding(16, 12, 16, 12);

            sortArea.addView(item);
        }
        problemDone = false;
        moves = 0;

        setFasttimes(level.getNumItems()*300, level.getNumItems()*500, level.getNumItems()*750);

        startHint(level.getNumItems());
    }


    @Override
    public boolean onDrag(View view, DragEvent event) {

        boolean islayout = view instanceof GridLayout;
        switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:
                // do nothing
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                if (!islayout) {
                    view.setBackgroundColor(Color.BLUE);
                    //view.setBackgroundResource(R.drawable.sort_drop_target);
                }
                break;
            case DragEvent.ACTION_DRAG_EXITED:

                if (!islayout) view.setBackgroundColor(BACKGROUND_COLOR);
                break;
            case DragEvent.ACTION_DROP:
                if (!islayout) view.setBackgroundColor(BACKGROUND_COLOR);
                // Dropped, reassign View to ViewGroup
                View view2 = (View) event.getLocalState();
                if (view2 == view) {
                    // Log.d("sort", "self drop");
                    break;
                }

                ViewGroup owner = (ViewGroup) view2.getParent();

                GridLayout container;
                if (view instanceof GridLayout) {
                    container = (GridLayout) view;

                } else {
                    container = (GridLayout) view.getParent();

                }

                int index = -1;
                for (int i = 0; i < container.getChildCount(); i++) {
                    if (container.getChildAt(i) == view) {
                        index = i;
                    }
                }

                owner.removeView(view2);
                if (index == -1) {
                    container.addView(view2);
                } else {
                    container.addView(view2, index);
                }
                moves++;
                checkDone();

                break;
            case DragEvent.ACTION_DRAG_ENDED:
                if (!islayout) view.setBackgroundColor(BACKGROUND_COLOR);
                break;
            default:
                break;
        }
        return true;
    }


    private void checkDone() {
        if (!problemDone) {
            final GridLayout sortArea = findViewById(R.id.sort_area);

            boolean sorted = true;

            for (int i = 0; i < sortArea.getChildCount() - 1; i++) {
                int num1 = (int) sortArea.getChildAt(i).getTag();
                int num2 = (int) sortArea.getChildAt(i + 1).getTag();
                if (num1 > num2) {
                    sorted = false;
                    break;
                }

            }
            if (sorted) {
                cancelHint();
                problemDone = true;
                stopTimer();


                for (int i = 0; i < sortArea.getChildCount(); i++) {
                    final int c = i;
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            sortArea.getChildAt(c).setBackgroundColor(Color.GREEN);
                        }
                    }, (c + 1) * 1000 / sortArea.getChildCount());
                }


                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        finishDone();
                    }
                }, 1200);
            }
        }

    }

    private void finishDone() {
        String prob = "";
        for (int p : numlist) {
            prob += p + ";";
        }


        answerDone(true, prob, "sorted", "sorted");

    }

    /**
     *  Calculate the points from the current problem
     *  By default this is based on simply the level number.
     *
     *  Override this in each activity.
     *
     *  Range, in general:
     *    difficulty 1:   1 - 50
     *    difficulty 2:   up to 100
     *    difficulty 3:   up to 200
     *    difficulty 4:   up to 500
     *    difficulty 5:   up to 1000
     *
     * @return the points for the current problem
     */
    @Override
    protected int onCalculatePoints() {
        SortingLevel level = ((SortingLevel) getLevel());
        if (moves == 0 ) moves =1; //failsafe
        return super.onCalculatePoints() + (int) (Math.sqrt(level.getMaxNum()) * level.getNumItems() / moves);
    }

    private boolean isSorted(List<Integer> list) {
        boolean sorted = true;

        for (int i = 0; i < list.size() - 1; i++) {
            if (list.get(i) > list.get(i + 1)) {
                sorted = false;
                break;
            }
        }
        return sorted;
    }

    private void markSorted() {
        final GridLayout sortArea = findViewById(R.id.sort_area);

        for (int i = 0; i < sortArea.getChildCount() - 1; i++) {
            int num1 = (int) sortArea.getChildAt(i).getTag();
            int num2 = (int) sortArea.getChildAt(i + 1).getTag();
            if (num1 > num2) {
                //sortArea.getChildAt(i).setBackgroundColor(Color.CYAN);
                sortArea.getChildAt(i+1).setBackgroundColor(Color.RED);
                //break;
            }

        }
    }

    @Override
    protected void onPerformHint(int hintTick) {
        markSorted();
        super.onPerformHint(hintTick);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (!problemDone && motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
            ClipData data = ClipData.newPlainText(view.getTag().toString(), view.getTag().toString());
            View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
            view.startDrag(data, shadowBuilder, view, 0);
            //view.setVisibility(View.INVISIBLE);
            return true;
        } else {
            return false;
        }
    }
} public class SortingLevel extends StdLevel {
    private final int mNumItems;
    private final int mMaxNum;

    public SortingLevel(int subjectkey, int numItems, int maxNum, int rounds) {
        super(subjectkey, rounds, InputMode.None);
        mNumItems = numItems;
        mMaxNum = maxNum;

    }

    public int getNumItems() {
        return mNumItems;
    }

    public int getMaxNum() {
        return mMaxNum;
    }

    @Override
    public String getDescription(Context context) {
        return context.getString(R.string.sort_desc, mNumItems, mMaxNum);
    }

    @Override
    public String getShortDescription(Context context) {
        return context.getString(R.string.sort_sdesc, mNumItems, mMaxNum);
    }
} public class PluralActivity extends StdGameActivity
        implements SubjectBaseActivity.AnswerGivenListener<String>,
        SubjectBaseActivity.AnswerTypedListener {


    private final int numanswers = 4;

    private List<String> words;
    private String word;
    private String answer;
    private String[] unpluralMap;
    private Map<String, String> pluralsMap;
    private String[] wordScores;

    public PluralActivity() {
        super(R.layout.std_plural_prob);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        words = Arrays.asList(getResources().getStringArray(R.array.common_nouns));

        unpluralMap = getResources().getStringArray(R.array.unplural);

        wordScores = getResources().getStringArray(R.array.plural_word_scores);

        pluralsMap = arrayPairsToMap(getResources().getStringArray(R.array.plurals));


    }

    private Map<String, String> arrayPairsToMap(String[] array) {

        if (array.length % 2 != 0) {
            throw new IllegalArgumentException("array to map must have even number of elements");
        }
        Map<String, String> map = new TreeMap<>();
        for (int j = 0; j < array.length; j += 2) {
            map.put(array[j], array[j + 1]);
        }
        return map;
    }

    @Override
    protected void onPause() {

        if (word!=null) {
            saveLevelValue("word", word);
        }
        super.onPause();
    }

    @Override
    protected void onResume() {

        super.onResume();
        if (isLandscape() && ((StdLevel) getLevel()).getInputMode() == InputMode.Input) {
            LinearLayout problem_area = findViewById(R.id.problem_area);
            problem_area.setOrientation(LinearLayout.HORIZONTAL);
        }

    }

    @Override
    protected void onShowLevel() {
        super.onShowLevel();


    }

    @Override
    protected void onShowProbImpl() {

        PluralLevel level = (PluralLevel) getLevel();
        word = getSavedLevelValue("word", (String) null);
        if (word == null) {
            int tries = 0;
            do {
                int score;
                do {
                    word = words.get(getRand(words.size() - 1));
                    score = scoreWord(word);
                    //System.out.println(word + " " + score + " " + pluralsMap.containsKey(word));
                }
                while (!pluralsMap.containsKey(word) || (getRand(10) > 2 && score < 2) || (getRand(10) > 3 && score < 3)); //try to get tricky words

            }
            while (tries++ < 200 && (word.length() < level.getMinWordLength() || word.length() > level.getMaxWordLength() || seenProblem(word)));
        } else {
            deleteSavedLevelValue("word");
        }

        int score = scoreWord(word);
        if (((PluralLevel) getLevel()).getInputMode() == InputMode.Buttons) {
            setFasttimes(800 + score*100, 900 + score*250, 1000 + score*320);
        } else {
            setFasttimes(900 + score*100, 1100 + score*300, 1400 + score*500);
        }


        answer = pluralsMap.get(word);
        Log.d("plural", word + " -> " + answer);

        TextView plural = findViewById(R.id.txtplural);
        plural.setText(capitalize(word));


        final TextView hint = findViewById(R.id.plurHint);
        hint.setText("");

        if (level.getInputMode() == InputMode.Buttons) {
            List<String> answers = getAnswerChoices(answer);

            makeChoiceButtons(getAnswerArea(), answers, this);

        } else if (level.getInputMode() == InputMode.Input) {

            makeInputBox(getAnswerArea(), getKeysArea(), this, INPUTTYPE_TEXT, 6, 0, word);

            startHint(word.length());

        } else {
            throw new IllegalArgumentException("Unknown inputMode! " + level.getInputMode());
        }

    }

    @Override
    public boolean onAnswerTyped(String answer) {
        return onAnswerGiven(answer);
    }

    @Override
    public boolean onAnswerGiven(String answer) {

        boolean isright = answer.toLowerCase().trim().equals(this.answer.toLowerCase());

        answerDone(isright, word, this.answer, answer.trim());

        return isright;
    }

    /**
     *  Calculate the points from the current problem
     *  By default this is based on simply the level number.
     *
     *  Override this in each activity.
     *
     *  Range, in general:
     *    difficulty 1:   1 - 50
     *    difficulty 2:   up to 100
     *    difficulty 3:   up to 200
     *    difficulty 4:   up to 500
     *    difficulty 5:   up to 1000
     *
     * @return the points for the current problem
     */
    @Override
    protected int onCalculatePoints() {
        float scoremult = answer.length() - getHintTicks();

        if (scoremult<=0) {  //if the hint is fully shown, give partial credit.
            scoremult=.3f;
        }
        return super.onCalculatePoints() + (int) (1 + word.length() * scoreWord(word) * scoremult);
    }


    private List<String> getAnswerChoices(String realanswer) {
        List<String> answers = new ArrayList<>();
        answers.add(realanswer);
        int maxtries = unpluralMap.length;
        int tries;
        do {
            String badspell;
            tries = 0;
            do {
                badspell = unplural(word);
            } while (tries++ < maxtries && answers.contains(badspell));
            if (tries < maxtries) {
                answers.add(badspell);
            }

        } while (answers.size() < numanswers && tries < maxtries);

        Collections.shuffle(answers);
        return answers;
    }

    @Override
    protected void onPerformHint(int hintTick) {
        final TextView hint = findViewById(R.id.plurHint);
        if (hintTick < answer.length()) {

            hint.setText(answer.substring(0, hintTick+1));

        } else {
            cancelHint();
        }
        super.onPerformHint(hintTick);

    }

    private String unplural(String word) {

        for (int j = 0; j < 1; j++) {
            int i = ((int) (Math.random() * ((unpluralMap.length - 1) / 2)) * 2);
            word = word.replaceFirst(unpluralMap[i], unpluralMap[i + 1]);
        }
        return word;
    }

    private int scoreWord(String candidate) {

        for (int difflevel = wordScores.length - 1; difflevel >= 0; difflevel--) {
            if (candidate.matches(wordScores[difflevel])) {
                return difflevel + 1;
            }

        }

        return 1;
    }

} public class PluralLevel extends StdLevel {

    private static final int wDiff = 3;
    private final int mMaxwordlength;
    private final int mMinwordlength;

    public PluralLevel(int subjectkey, int maxwordlength, int rounds, InputMode inputMode) {
        this(subjectkey, maxwordlength > wDiff ? maxwordlength - wDiff : 1, maxwordlength, rounds, inputMode);
    }

    private PluralLevel(int subjectkey, int minwordlength, int maxwordlength, int rounds, InputMode inputMode) {
        super(subjectkey, rounds, inputMode);

        mMinwordlength = minwordlength;
        mMaxwordlength = maxwordlength;

    }

    @Override
    public String getDescription(Context context) {
        return context.getString(R.string.length, mMaxwordlength) + " " + getInputModeString(context);
    }

    @Override
    public String getShortDescription(Context context) {
        return "Wl: " + mMaxwordlength;
    }

    public int getMaxWordLength() {
        return mMaxwordlength;
    }

    public int getMinWordLength() {
        return mMinwordlength;
    }


} public class SpellingActivity extends StdGameActivity
        implements TextToVoice.VoiceReadyListener,
        SubjectBaseActivity.AnswerGivenListener<String>,
        SubjectBaseActivity.AnswerTypedListener {


    private final Handler handler = new Handler();
    private final int numanswers = 4;
    private TextToVoice v;
    private List<String> words;

    private String word;
    private String[] unspellMap;


    private String wordStart;


    public SpellingActivity() {
        super(R.layout.std_spelling_prob);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        unspellMap = getResources().getStringArray(R.array.unspell);

        if (unspellMap.length % 2 != 0) {
            throw new IllegalArgumentException("unspell array must have even number of arguments");
        }

        super.onCreate(savedInstanceState);


        Button b = findViewById(R.id.btn_repeat);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                v.speak(word);
            }
        });
    }

    @Override
    protected void onPause() {
        if (v!=null) {
            v.stop();

            saveLevelValue("word", word);
        }
        super.onPause();
    }

    @Override
    protected void onResume() {
        //timer = new Timer();

        setReadyForProblem(false);
        View problem_area = findViewById(R.id.problem_area);
        problem_area.setVisibility(View.VISIBLE);
        findViewById(R.id.spelling_problem_area).setVisibility(View.INVISIBLE);
        findViewById(R.id.spell_loading).setVisibility(View.VISIBLE);

        super.onResume();


        if (isLandscape()) {
            LinearLayout spelling_problem_area = findViewById(R.id.spelling_problem_area);
            spelling_problem_area.setOrientation(LinearLayout.HORIZONTAL);
        }

        v = ((Primary) getApplicationContext()).getTextToVoice();
        v.setVoiceReadyListener(this);
        if (v.isReady()) {
            setWeReady();
        }


    }

    @Override
    protected void onShowLevel() {
        super.onShowLevel();
        words = Arrays.asList(getResources().getStringArray(((SpellingLevel) getLevel()).getmWordlistId()));

        SpellingLevel level = (SpellingLevel) getLevel();
        int fac = level.getMaxwordlength();
        if (level.getInputMode() == InputMode.Buttons) {
            setFasttimes(600 + 50*fac, 700 + 75*fac, 800 + 125*fac);

        } else {
            setFasttimes(800 + 200*fac, 900 + 400*fac, 1000 + 600*fac);
        }

    }

    @Override
    protected void onBeforeShowProb() {

    }
    @Override
    protected void onShowProbImpl() {

        word = getSavedLevelValue("word", (String) null);
        if (word == null) {
            int tries = 0;
            do {
                word = words.get(getRand(words.size() - 1));
            } while (tries++ < 50 && seenProblem(word));
        } else {
            deleteSavedLevelValue("word");
        }

        Log.d("spell", word);

        SpellingLevel level = (SpellingLevel) getLevel();
        if (level.getInputMode() == InputMode.Input) {

            makeInputBox(getAnswerArea(), getKeysArea(), SpellingActivity.this, INPUTTYPE_TEXT, 6, 0);
        }

        v.speak(word);
        ((TextView) findViewById(R.id.spell_hint)).setText("");

    }

    @Override
    public boolean onAnswerTyped(String answer) {
        return onAnswerGiven(answer);
    }

    @Override
    public boolean onAnswerGiven(String answer) {

        boolean isright = answer.toLowerCase().trim().equals(word.toLowerCase());

        answerDone(isright, word, word, answer.trim());

        return isright;
    }

    /**
     *  Calculate the points from the current problem
     *  By default this is based on simply the level number.
     *
     *  Override this in each activity.
     *
     *  Range, in general:
     *    difficulty 1:   1 - 50
     *    difficulty 2:   up to 100
     *    difficulty 3:   up to 200
     *    difficulty 4:   up to 500
     *    difficulty 5:   up to 1000
     *
     * @return the points for the current problem
     */
    @Override
    protected int onCalculatePoints() {
        float scoremult = word.length() - getHintTicks();

        if (scoremult<=0) {  //if the hint is fully shown, give partial credit.
            scoremult=.3f;
        }
        return super.onCalculatePoints() + (int) (1 + word.length()  * scoremult);

    }

    @Override
    public void onVoiceReady(TextToVoice ttv) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                setWeReady();
            }
        });
        Log.d("sp1", "onVoiceReady called");
    }

    private void setWeReady() {
        findViewById(R.id.spelling_problem_area).setVisibility(View.VISIBLE);
        findViewById(R.id.spell_loading).setVisibility(View.GONE);
        setReadyForProblem(true);
    }

    @Override
    public void onSpeakComplete(TextToVoice ttv) {
        if (wordStart == null || !wordStart.equals(word)) {
            startTimer();
            startHint(word.length());
            wordStart = word;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    SpellingLevel level = (SpellingLevel) getLevel();
                    if (level.getInputMode() == InputMode.Buttons) {
                        List<String> answers = getAnswerChoices(word);

                        makeChoiceButtons(getAnswerArea(), answers, SpellingActivity.this);

                    }
                }
            });
        }
    }

    @Override
    protected void onPerformHint(int hintTick) {
        final TextView hint = findViewById(R.id.spell_hint);
        if (hintTick < word.length()) {


            hint.setText(word.substring(0, hintTick+1));

            if (hintTick == 1 || (word.length() > 3 && hintTick == word.length())) {
                v.speak(word);
            }

        } else {
            cancelHint();
        }
        super.onPerformHint(hintTick);

    }

    @Override
    public void onError(TextToVoice ttv) {

    }

    private List<String> getAnswerChoices(String realanswer) {
        List<String> answers = new ArrayList<>();
        int maxtries = unspellMap.length;
        int tries;
        do {
            String badspell;
            tries = 0;
            do {
                badspell = unspell(word);
            } while (tries++ < maxtries && answers.contains(badspell));
            if (tries < maxtries) {
                answers.add(badspell);
            }

        } while (answers.size() < numanswers && tries < maxtries);

        Collections.shuffle(answers);
        return answers;
    }


    private String unspell(String word) {

        for (int j = 0; j < 1; j++) {
            int i = ((int) (Math.random() * ((unspellMap.length - 1) / 2)) * 2);

            String find = unspellMap[i];
            String replacement = unspellMap[i + 1];

            word = word.replaceFirst(find, replacement);
        }
        return word;
    }


} public class SpellingLevel extends StdLevel {

    private final int mWordlistId;
    private final int mMaxwordlength;


    public SpellingLevel(int subjectkey, int wordlistid, int maxwordlength, int rounds, InputMode inputMode) {
        super(subjectkey, rounds, inputMode);
        mWordlistId = wordlistid;
        mMaxwordlength = maxwordlength;

    }


    @Override
    public String getDescription(Context context) {
        return context.getString(R.string.length, mMaxwordlength) + " " + getInputModeString(context);
    }

    @Override
    public String getShortDescription(Context context) {
        return "Wl: " + mMaxwordlength;
    }

    public int getmWordlistId() {
        return mWordlistId;
    }

    public int getMaxwordlength() {
        return mMaxwordlength;
    }
} public class TimeActivity extends StdGameActivity implements SubjectBaseActivity.AnswerGivenListener, SubjectBaseActivity.AnswerTypedListener {

    private int mClockwidth = 300;
    private int mHour;
    private int mMinute;

    public TimeActivity() {
        super(R.layout.std_time_prob);

    }

    @Override
    protected void onPause() {
        super.onPause();
        saveLevelValue("mHour", mHour);
        saveLevelValue("mMinute", mMinute);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Point ss = getScreenSize();
        mClockwidth = Math.min(ss.x, ss.y)/2;

        if (isLandscape()) {
            mClockwidth *=.8;
            TimeLevel level = (TimeLevel)getLevel();
            if (level.getInputMode() == InputMode.Input) {
                LinearLayout meta_center = findViewById(R.id.meta_center);
                meta_center.setOrientation(LinearLayout.HORIZONTAL);
            }
        }
        addToNumpadKeyMap(".",":");
        setMaxSeenSize(12);

    }


    @Override
    protected void onShowLevel() {
        super.onShowLevel();
        clearSeenProblem();
        TimeLevel level = (TimeLevel)getLevel();
        if (level.useFuzzy() && level.getMinuteGranularity()== TimeLevel.MinuteGranularity.One) {
            TextView t = findViewById(R.id.txt_time_header);
            t.setText(R.string.fuzzy_closest_message);
        }

        int fac = level.getMinuteGranularity().ordinal() + 1 + (level.useFuzzy()?1:0);
        if (level.getInputMode() == InputMode.Buttons) {

            setFasttimes(600 + 250*fac, 800 + 250*fac, 1100 + 225*fac);
        } else {
            setFasttimes(1000 + 250*fac, 1300 + 250*fac, 1500 + 275*fac);
        }

    }

    @Override
    protected void onShowProbImpl() {
        mHour = getSavedLevelValue("mHour", -1);
        mMinute = getSavedLevelValue("mMinute", -1);
        if (mMinute ==-1 || mHour ==-1 ) {
            int tries = 0;
            do {
                mHour = getRand(1, 12);
                mMinute = getMinutes();
            } while (tries++ < 100 && seenProblem(mHour, mMinute));
        } else {
            deleteSavedLevelValue("mHour");
            deleteSavedLevelValue("mMinute");
        }

        TimeLevel level = (TimeLevel)getLevel();
        Bitmap bitmap = getClockBitmap(mHour, mMinute);

        ImageView timeimage = findViewById(R.id.timeimage);
        timeimage.setImageBitmap(bitmap);

        List<String> answers = getAnswers(level);

        Collections.shuffle(answers);

        if (level.getInputMode()== InputMode.Buttons) {
            makeChoiceButtons(getAnswerArea(), answers, this);
        } else if (level.getInputMode()== InputMode.Input) {
            makeInputBox(getAnswerArea(), getKeysArea(), this, INPUTTYPE_TIME, 3, 0);
            startHint(level.getLevelNum()+1);
        }
        TextView timeHint = findViewById(R.id.timeHint);
        timeHint.setText("");

    }

    @NonNull
    private List<String> getAnswers(TimeLevel level) {
        Set<String> answerset = new TreeSet<>();
        answerset.add(formatTime(mHour,mMinute));

        int mindiff = 8;
        do {
            int thour = getRand(1,12);
            int tminute = getMinutes();
            if (getRand(10)>8 && mHour<12) {
                thour = mHour + 1;
                tminute = mMinute;
            } else if (getRand(10)>7 && mHour>1) {
                thour = mHour - 1;
                tminute = mMinute;
            } else if (getRand(10)>5) {
                int t = mHour;
                if (mMinute==0) {
                    thour = 12;
                } else {
                    thour = (mMinute+5) / 5;
                }
                if (level.getMinuteGranularity()!= TimeLevel.MinuteGranularity.Hour) {
                    tminute = (t-1) * 5;
                }
            } else if (getRand(10)>6 && (
                       level.getMinuteGranularity()== TimeLevel.MinuteGranularity.Five
                    || level.getMinuteGranularity()== TimeLevel.MinuteGranularity.One)) {
                thour = mHour;
                if (mMinute<60-mindiff*2 && getRand(10)>5) {
                    tminute = mMinute + getRand(mindiff, mindiff*2);

                } else if (mMinute>mindiff*2 && getRand(10)>5) {
                    tminute = mMinute - getRand(mindiff, mindiff*2);
                }
            }
            if (!(thour == mHour && Math.abs(tminute - mMinute)<mindiff) ) {
                answerset.add(formatTime(thour,tminute));
            }
        } while (answerset.size()<4);

        return new ArrayList<>(answerset);
    }

    private String makeFuzzyTime(String time) {

        String fuzzytime = "";
        String [] tparts = time.split(":");
        int hour = Integer.parseInt(tparts[0]);
        int min = Integer.parseInt(tparts[1]);

        int disphour = hour;
        int dispmin = min;

        boolean noMins = false;
        String befaft = "";
        if (min <= 27) {
            befaft = getString(R.string.fuzzy_after);
            disphour = hour;
            dispmin = min;
        } else if (min >= 33) {
            befaft = getString(R.string.fuzzy_till);
            disphour = hour==12 ? 1 : hour+1;
            dispmin = 60 - min;

        } else {
            befaft = getString(R.string.fuzzy_half_past);
            noMins = true;
        }
        if (min >3 && min<57) {
            for (int t = 5; t < 31; t += 5) {
                if (Math.abs(dispmin - t) <= 2) {
                    if (!noMins) {
                        if (t==15 || t==45) {
                            fuzzytime = getString(R.string.fuzzy_quarter);
                        } else {
                            fuzzytime = t + "";
                        }
                    }
                    fuzzytime += befaft + disphour;
                    break;
                }
            }
        } else {
            fuzzytime = disphour + getString(R.string.fuzzy_o_clock);
        }

        if (fuzzytime.length() == 0) {
            Log.d("Timeact", "zero length for " + time);
            fuzzytime = "0";
        }
        return fuzzytime;
    }

    @Override
    protected void onPerformHint(int hintTick) {
        String time = formatTime(mHour,mMinute);
        if (hintTick <time.length()) {
            TextView timeHint = findViewById(R.id.timeHint);
            timeHint.setText(time.substring(0, hintTick+1));
        }
        super.onPerformHint(hintTick);
    }

    private int getMinutes() {
        int tminute;
        switch ( ((TimeLevel)getLevel()).getMinuteGranularity() ) {
            case Hour:
                tminute = 0;
                break;
            case Half:
                tminute = getRand(1)*30;
                break;
            case Quarter:
                tminute = getRand(1,3)*15;
                break;
            case Five:
                tminute = getRand(1,11)*5;
                break;
            case One:
            default:
                tminute = getRand(1,59);
                break;
        }
        return tminute;
    }


    @Override
    public boolean onAnswerTyped(String answer) {
        return onAnswerGiven(answer);
    }

    @Override
    public boolean onAnswerGiven(Object answer) {


        String ranswer = formatTime(mHour,mMinute);
        String uanswer = ((String)answer);

        //Allow user to just type 9 instead of 9:00
        //I might add seconds later, the double replace doesn't hurt now.
        String ranswer2 = ranswer.replaceFirst(":00$","").replaceFirst(":00$","");
        String uanswer2 = uanswer.replaceFirst(":00$","").replaceFirst(":00$","");

        boolean isright = ranswer2.equals(uanswer2);

        answerDone(isright, ranswer, ranswer, uanswer);
        return isright;
    }

    /**
     *  Calculate the points from the current problem
     *  By default this is based on simply the level number.
     *
     *  Override this in each activity.
     *
     *  Range, in general:
     *    difficulty 1:   1 - 50
     *    difficulty 2:   up to 100
     *    difficulty 3:   up to 200
     *    difficulty 4:   up to 500
     *    difficulty 5:   up to 1000
     *
     * @return the points for the current problem
     */
    @Override
    protected int onCalculatePoints() {

        TimeLevel level = (TimeLevel)getLevel();
        return super.onCalculatePoints() * 50 * (level.getMinuteGranularity().ordinal()+1);
    }

    private String formatTime(int hour, int minute) {
        String time = String.format(Locale.getDefault(),"%d:%02d", hour, minute);
        TimeLevel level = (TimeLevel)getLevel();
        if (level.useFuzzy()) {
            time = makeFuzzyTime(time);
        }
        return time;
    }

    private Bitmap getClockBitmap(int hour, int minute) {
        return getClockBitmap(hour, minute, -1);
    }

    private Bitmap getClockBitmap(int hour, int minute, int second) {
        Paint black = new Paint();
        black.setARGB(255,0,0,0);
        black.setStrokeWidth(mClockwidth /60);
        black.setStyle(Paint.Style.STROKE);

        Paint hourColor = new Paint();
        hourColor.setARGB(255,0,0,200);
        hourColor.setStrokeWidth(mClockwidth /40);
        hourColor.setStyle(Paint.Style.STROKE);


        //System.out.println(mClockwidth);
        Paint hourColorThin = new Paint(hourColor);
        hourColorThin.setStrokeWidth(3);
        hourColorThin.setTextSize(Math.min(28, mClockwidth/8));
        hourColorThin.setTextAlign(Paint.Align.CENTER);

        Paint minuteColor = new Paint();
        minuteColor.setARGB(255,0,200,0);
        minuteColor.setStrokeWidth(mClockwidth /52);
        minuteColor.setStyle(Paint.Style.STROKE);

        Paint minuteColorThin = new Paint(minuteColor);
        minuteColorThin.setStrokeWidth(3);


        Paint secondColor = new Paint();
        secondColor.setARGB(255,180,0,64);
        secondColor.setStrokeWidth(2);
        secondColor.setStyle(Paint.Style.STROKE);

        int centerX = mClockwidth /2;
        int centerY = mClockwidth /2;
        int radius = mClockwidth /2 - 10;

        double hourRadian = 2 * Math.PI / 12;
        double minuteRadian = 2 * Math.PI / 60;
        double secondRadian = 2 * Math.PI / 60;

        Bitmap bitmap = Bitmap.createBitmap(mClockwidth, mClockwidth, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        for (int mintick=1; mintick<=60; mintick++) {

            float dX = (float)(Math.cos(minuteRadian * mintick - Math.PI/2));
            float dY = (float)(Math.sin(minuteRadian * mintick- Math.PI/2));

            canvas.drawLine(centerX + dX*radius, centerY + dY*radius, centerX + dX*(radius-10), centerY + dY*(radius-10), minuteColorThin);
            if (mintick % 5 == 0) {
                float iX = centerX + dX * (radius - 15);
                float iY = centerY + dY * (radius - 15);
                float itX = centerX + dX * (radius - 30);
                float itY = centerY + dY * (radius - 30) + 10;
                canvas.drawLine(centerX + dX * radius, centerY + dY * radius, iX, iY, hourColorThin);
                canvas.drawText((mintick/5)+"", itX, itY, hourColorThin);

            }
        }

        double hourAdj = minute/60.0;

        float hX = (float)(Math.cos(hourRadian * (hour+hourAdj) - Math.PI/2) * (radius*.5));
        float hY = (float)(Math.sin(hourRadian * (hour+hourAdj) - Math.PI/2) * (radius*.5));
        canvas.drawLine(centerX, centerY, centerX+hX, centerY+hY, hourColor);

        double minuteAdj = 0;
        if (second!=-1) {
            minuteAdj = second/60.0;
        }

        float mX = (float)(Math.cos(minuteRadian * (minute+minuteAdj) - Math.PI/2) * (radius*.8));
        float mY = (float)(Math.sin(minuteRadian * (minute+minuteAdj) - Math.PI/2) * (radius*.8));
        canvas.drawLine(centerX, centerY, centerX+mX, centerY+mY, minuteColor);

        if (second!=-1) {
            float sX = (float) (Math.cos(secondRadian * second - Math.PI / 2) * (radius * .95));
            float sY = (float) (Math.sin(secondRadian * second - Math.PI / 2) * (radius * .95));
            canvas.drawLine(centerX, centerY, centerX + sX, centerY + sY, secondColor);
        }

        canvas.drawCircle(centerX, centerY, 4, black);
        canvas.drawCircle(centerX, centerY, radius, black);
        return bitmap;
    }


} public class TimeLevel extends StdLevel {

    public enum MinuteGranularity {
        Hour(R.string.min_gran_hour),
        Half(R.string.min_gran_half),
        Quarter(R.string.min_gran_quarter),
        Five(R.string.min_gran_five),
        One(R.string.min_gran_one);

        private final int mResId;
        MinuteGranularity(int resId) {
            mResId = resId;
        }

        String getString(Context context) {
            return context.getString(mResId);
        }
    }
    //public enum SecondGranularity {Minute, Half, Quarter, Five, One}

    private final MinuteGranularity mMinuteGranularity;

    private final boolean mFuzzy;
    public TimeLevel(int subjectkey, MinuteGranularity minGran, int rounds, InputMode inputMode) {
        this(subjectkey, minGran, rounds, inputMode, false);
    }
    public TimeLevel(int subjectkey, MinuteGranularity minGran, int rounds, InputMode inputMode, boolean useFuzzy) {
        super(subjectkey, rounds, inputMode);
        mMinuteGranularity = minGran;
        mFuzzy = useFuzzy;
    }

    public boolean useFuzzy() {
        return mFuzzy;
    }

    public MinuteGranularity getMinuteGranularity() {
        return mMinuteGranularity;
    }

    @Override
    public String getDescription(Context context) {
        return mMinuteGranularity.getString(context) + ". "  + getInputModeString(context);
    }

    @Override
    public String getShortDescription(Context context) {
        return mMinuteGranularity.toString();
    }
}
import android.content.Context;
import com.quaap.primary.base.Level;
import com.quaap.primary.base.component.InputMode;
import com.quaap.primary.base.data.SubjectGroup;
import com.quaap.primary.base.data.Subjects;
import com.quaap.primary.math.BasicMathActivity;
import com.quaap.primary.math.BasicMathLevel;
import com.quaap.primary.math.MathOp;
import com.quaap.primary.math.Negatives;
import com.quaap.primary.math.SortingActivity;
import com.quaap.primary.math.SortingLevel;
import com.quaap.primary.partsofspeech.plurals.PluralActivity;
import com.quaap.primary.partsofspeech.plurals.PluralLevel;
import com.quaap.primary.spelling.SpellingActivity;
import com.quaap.primary.spelling.SpellingLevel;
import com.quaap.primary.timemoney.TimeActivity;
import com.quaap.primary.timemoney.TimeLevel;
package com.quaap.primary;



/**
 * Created by tom on 12/15/16.
 * <p>
 * Copyright (C) 2016   Tom Kliethermes
 * <p>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
public class Levels {

    //these get loaded by base.data.Subjects.


    public static Subjects.Desc[] getSubjectInstances(Context context) {
        Subjects.Desc[] subject = new Subjects.Desc[]{
                new Subjects.Desc(context, SubjectGroup.Math, R.string.subject_m1_code, R.string.subject_m1_name, R.string.subject_m1_desc,
                        BasicMathActivity.class,
                        new Level[]{

                                new BasicMathLevel(R.string.subject_m1_code, MathOp.Plus, 5, 10, InputMode.Buttons),
                                new BasicMathLevel(R.string.subject_m1_code, MathOp.Minus, 5, 10, InputMode.Buttons),

                                new BasicMathLevel(R.string.subject_m1_code, MathOp.Plus, 10, 20, InputMode.Buttons),
                                new BasicMathLevel(R.string.subject_m1_code, MathOp.Minus, 10, 20, InputMode.Buttons),

                                new BasicMathLevel(R.string.subject_m1_code, MathOp.Minus, MathOp.Plus, 10, Negatives.None, 20, true, InputMode.Buttons),