<commit_before>/*
 *    Copyright 2021 <NAME>
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.noprestige.kanaquiz.questions;

import com.noprestige.kanaquiz.R;
import com.noprestige.kanaquiz.logs.LogDao;
import com.noprestige.kanaquiz.logs.LogTypeConversion;
import com.noprestige.kanaquiz.options.OptionsControl;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ArrayBlockingQueue;

public class QuestionBank extends WeightedList<Question>
{
    private Question currentQuestion;
    private Set<String> fullKanaAnswerList = new TreeSet<>(new GojuonOrder());
    private Map<String, WeightedList<String>> weightedAnswerListCache;

    private Set<String> basicAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> diacriticAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> digraphAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> diacriticDigraphAnswerList = new TreeSet<>(new GojuonOrder());

    private String[] currentPossibleAnswers;

    private Set<String> wordAnswerList = new TreeSet<>();
    private Set<String> yomiAnswerList = new TreeSet<>();

    private static final int MAX_MULTIPLE_CHOICE_ANSWERS = 6;

    private QuestionRecord previousQuestions;
    private int maxKanaAnswerWeight = -1;
    private int maxWordAnswerWeight = -1;
    private int maxYomiAnswerWeight = -1;

    public QuestionType getCurrentQuestionType()
    {
        return currentQuestion.getType();
    }

    public void newQuestion()
    {
        if (count() > 0)
        {
            if (previousQuestions == null)
                previousQuestions =
                        new QuestionRecord(Math.min(count(), OptionsControl.getInt(R.string.prefid_repetition)));
            do
                currentQuestion = getRandom();
            while (!previousQuestions.add(currentQuestion));
            currentPossibleAnswers = null;
        }
    }

    static class QuestionRecord extends ArrayBlockingQueue<String>
    {
        QuestionRecord(int capacity)
        {
            super(capacity);
        }

        public boolean add(Question question)
        {
            char prefix = LogTypeConversion.toCharFromType(question.getType());
            if (contains(prefix + question.getDatabaseKey()))
                return false;
            else
            {
                add(prefix + question.getDatabaseKey());
                if (remainingCapacity() == 0)
                    remove();
                return true;
            }
        }
    }

    public String getCurrentQuestionText()
    {
        return currentQuestion.getQuestionText();
    }

    public String getCurrentQuestionKey()
    {
        return currentQuestion.getDatabaseKey();
    }

    public boolean checkCurrentAnswer(String response)
    {
        return currentQuestion.checkAnswer(response);
    }

    public String fetchCorrectAnswer()
    {
        return currentQuestion.fetchCorrectAnswer();
    }

    public boolean loadQuestion(String questionKey, QuestionType type)
    {
        if (previousQuestions == null)
            previousQuestions =
                    new QuestionRecord(Math.min(count(), OptionsControl.getInt(R.string.prefid_repetition)));

        Collection<Question> questions = values();

        for (Question thisQuestion : questions)
        {
            if (thisQuestion.getDatabaseKey().equals(questionKey) && (thisQuestion.getType() == type))
            {
                currentQuestion = thisQuestion;
                previousQuestions.add(thisQuestion);
                currentPossibleAnswers = null;
                return true;
            }
        }
        return false;
    }

    public boolean addQuestions(Question[] questions)
    {
        if (questions != null)
        {
            boolean returnValue = true;
            Set<String> kanjiQuestionTypePref = null;
            for (Question question : questions)
                // if any one of the additions fail, the method returns false
                if (question.getClass().equals(KanjiQuestion.class))
                {
                    if (kanjiQuestionTypePref == null)
                        kanjiQuestionTypePref = OptionsControl.getStringSet(R.string.prefid_kanji_question_type);
                    if (kanjiQuestionTypePref.contains("meaning"))
                        returnValue = addQuestion(question) && returnValue;
                    if (kanjiQuestionTypePref.contains("kunyomi"))
                        returnValue = addQuestion(((KanjiQuestion) question).getKunYomiQuestion()) && returnValue;
                    if (kanjiQuestionTypePref.contains("onyomi"))
                        returnValue = addQuestion(((KanjiQuestion) question).getOnYomiQuestion()) && returnValue;
                }
                else
                    returnValue = addQuestion(question) && returnValue;
            return returnValue;
        }
        return false;
    }

    public boolean addQuestion(Question question)
    {
        weightedAnswerListCache = null;
        previousQuestions = null;
        maxKanaAnswerWeight = -1;
        maxWordAnswerWeight = -1;
        maxYomiAnswerWeight = -1;
        if (question != null)
        {
            // Fetches the percentage of times the user got a question right,
            Float percentage = LogDao.getQuestionPercentage(question.getDatabaseKey(), question.getType());
            if (percentage == null)
                percentage = 0.1f;
            // The 1f is to invert the value so we get the number of times they got it wrong,
            // Times 100f to get the percentage.
            int weight = (int) Math.ceil((1f - percentage) * 100f);
            // Setting weight to never get lower than 2,
            // so any question the user got perfect will still appear in the quiz.
            if (weight < 2)
                weight = 2;
            // if any aspect of the addition fails, the method returns false
            boolean returnValue = add(weight, question);
            QuestionType type = question.getType();
            if ((type == QuestionType.VOCABULARY) || (type == QuestionType.KANJI))
                returnValue = wordAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
            else if ((type == QuestionType.KUN_YOMI) || (type == QuestionType.ON_YOMI))
                returnValue = yomiAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
            else if (type == QuestionType.KANA)
            {
                returnValue = fullKanaAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
                // Storing answers in specialized answer lists for more specialized answer selection
                returnValue = getSpecialList((KanaQuestion) question).add(question.fetchCorrectAnswer()) && returnValue;
            }

            return returnValue;
        }
        return false;
    }

    private Set<String> getSpecialList(KanaQuestion question)
    {
        if (question.isDiacritic() && question.isDigraph())
            return diacriticDigraphAnswerList;
        else if (question.isDiacritic())<commit_msg>Made sound questions compatible with finer question selection<commit_after>/*
 *    Copyright 2021 <NAME>
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.noprestige.kanaquiz.questions;

import com.noprestige.kanaquiz.R;
import com.noprestige.kanaquiz.logs.LogDao;
import com.noprestige.kanaquiz.logs.LogTypeConversion;
import com.noprestige.kanaquiz.options.OptionsControl;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ArrayBlockingQueue;

public class QuestionBank extends WeightedList<Question>
{
    private Question currentQuestion;
    private Set<String> fullKanaAnswerList = new TreeSet<>(new GojuonOrder());
    private Map<String, WeightedList<String>> weightedAnswerListCache;

    private Set<String> basicAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> diacriticAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> digraphAnswerList = new TreeSet<>(new GojuonOrder());
    private Set<String> diacriticDigraphAnswerList = new TreeSet<>(new GojuonOrder());

    private String[] currentPossibleAnswers;

    private Set<String> wordAnswerList = new TreeSet<>();
    private Set<String> yomiAnswerList = new TreeSet<>();

    private static final int MAX_MULTIPLE_CHOICE_ANSWERS = 6;

    private QuestionRecord previousQuestions;
    private int maxKanaAnswerWeight = -1;
    private int maxWordAnswerWeight = -1;
    private int maxYomiAnswerWeight = -1;

    public QuestionType getCurrentQuestionType()
    {
        return currentQuestion.getType();
    }

    public void newQuestion()
    {
        if (count() > 0)
        {
            if (previousQuestions == null)
                previousQuestions =
                        new QuestionRecord(Math.min(count(), OptionsControl.getInt(R.string.prefid_repetition)));
            do
                currentQuestion = getRandom();
            while (!previousQuestions.add(currentQuestion));
            currentPossibleAnswers = null;
        }
    }

    static class QuestionRecord extends ArrayBlockingQueue<String>
    {
        QuestionRecord(int capacity)
        {
            super(capacity);
        }

        public boolean add(Question question)
        {
            char prefix = LogTypeConversion.toCharFromType(question.getType());
            if (contains(prefix + question.getDatabaseKey()))
                return false;
            else
            {
                add(prefix + question.getDatabaseKey());
                if (remainingCapacity() == 0)
                    remove();
                return true;
            }
        }
    }

    public String getCurrentQuestionText()
    {
        return currentQuestion.getQuestionText();
    }

    public String getCurrentQuestionKey()
    {
        return currentQuestion.getDatabaseKey();
    }

    public boolean checkCurrentAnswer(String response)
    {
        return currentQuestion.checkAnswer(response);
    }

    public String fetchCorrectAnswer()
    {
        return currentQuestion.fetchCorrectAnswer();
    }

    public boolean loadQuestion(String questionKey, QuestionType type)
    {
        if (previousQuestions == null)
            previousQuestions =
                    new QuestionRecord(Math.min(count(), OptionsControl.getInt(R.string.prefid_repetition)));

        Collection<Question> questions = values();

        for (Question thisQuestion : questions)
        {
            if (thisQuestion.getDatabaseKey().equals(questionKey) && (thisQuestion.getType() == type))
            {
                currentQuestion = thisQuestion;
                previousQuestions.add(thisQuestion);
                currentPossibleAnswers = null;
                return true;
            }
        }
        return false;
    }

    public boolean addQuestions(Question[] questions)
    {
        if (questions != null)
        {
            boolean returnValue = true;
            for (Question question : questions)
                // if any one of the additions fail, the method returns false
                returnValue = addQuestion(question) && returnValue;
            return returnValue;
        }
        return false;
    }

    public boolean addQuestion(Question question)
    {
        weightedAnswerListCache = null;
        previousQuestions = null;
        maxKanaAnswerWeight = -1;
        maxWordAnswerWeight = -1;
        maxYomiAnswerWeight = -1;
        if (question != null)
        {
            if (question.getClass().equals(KanjiQuestion.class))
            {
                boolean returnValue = true;
                Set<String> kanjiQuestionTypePref = OptionsControl.getStringSet(R.string.prefid_kanji_question_type);
                if (kanjiQuestionTypePref.contains("meaning"))
                    returnValue = addQuestionInternal(question) && returnValue;
                if (kanjiQuestionTypePref.contains("kunyomi"))
                    returnValue = addQuestionInternal(((KanjiQuestion) question).getKunYomiQuestion()) && returnValue;
                if (kanjiQuestionTypePref.contains("onyomi"))
                    returnValue = addQuestionInternal(((KanjiQuestion) question).getOnYomiQuestion()) && returnValue;
                return returnValue;
            }
            else
                return addQuestionInternal(question);
        }
        return false;
    }

    private boolean addQuestionInternal(Question question)
    {
        // Fetches the percentage of times the user got a question right,
        Float percentage = LogDao.getQuestionPercentage(question.getDatabaseKey(), question.getType());
        if (percentage == null)
            percentage = 0.1f;
        // The 1f is to invert the value so we get the number of times they got it wrong,
        // Times 100f to get the percentage.
        int weight = (int) Math.ceil((1f - percentage) * 100f);
        // Setting weight to never get lower than 2,
        // so any question the user got perfect will still appear in the quiz.
        if (weight < 2)
            weight = 2;
        // if any aspect of the addition fails, the method returns false
        boolean returnValue = add(weight, question);
        QuestionType type = question.getType();
        if ((type == QuestionType.VOCABULARY) || (type == QuestionType.KANJI))
            returnValue = wordAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
        else if ((type == QuestionType.KUN_YOMI) || (type == QuestionType.ON_YOMI))
            returnValue = yomiAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
        else if (type == QuestionType.KANA)
        {
            returnValue = fullKanaAnswerList.add(question.fetchCorrectAnswer()) && returnValue;
            // Storing answers in specialized answer lists for more specialized answer selection
            returnValue = getSpecialList((KanaQuestion) question).add(question.fetchCorrectAnswer()) && returnValue;
        }

        return returnValue;
    }

    private Set<String> getSpecialList(KanaQuestion question)
    {
        if (question.isDiacritic() && question.isDigraph())
            return diacriticDigraphAnswerList;
        else if (question.isDiacritic())
{'fraction_non_alphanumeric': 0.08690087668778397, 'fraction_numerical': 0.003839508542906508, 'mean_word_length': 2.3652024117140398, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2015953', 'n_tokens_mistral': 4107, 'n_tokens_neox': 3858, 'n_words': 1044}