public class Application extends android.app.Application {

    public static DiComponent COMPONENT;

    @Override
    public void onCreate() {
        super.onCreate();
        initIconify();
        initActiveAndroid();
        initDagger();
    }

    private void initDagger() {
        COMPONENT = DaggerDiComponent.builder()
                .appModule(new AppModule())
                .build();
    }

    private void initIconify() {
        //FontAwesomeIcons.fa_volume_up
        Iconify.with(new FontAwesomeModule());
    }

    private void initActiveAndroid() {
        ActiveAndroid.initialize(this);
    }

} public final class Constants {
    public Constants() throws Exception {
        throw new Exception();
    }

    /**
     * Make Arabic fonts slightly bigger (because their fonts are tiny and difficult to read for a
     * nonarabic reader).
     */
    public static final String[] LANGS_WITH_LARGER_FONTS = new String[]{"ar"};
} public class Dao {

    @Inject
    public Dao() {
    }

    public void importLanguage(Language language) {
        language.save();
    }

    public void importCollection(SentenceCollection col) {
        SentenceCollection sentenceCollection = new Select()
                .from(SentenceCollection.class)
                .where("collection_id = ?", col.getCollectionID())
                .executeSingle();
        if (sentenceCollection != null) {
            sentenceCollection
                    .setType(col.getType())
                    .setFilename(col.getFilename())
                    .save();
        } else {
            col.save();
        }
    }

    public void importSentences(List<Sentence> sentences) {

        List<String> ids = new ArrayList<>();
        for (Sentence sentence : sentences) {
            ids.add(sentence.getSentenceId());
        }

        List<Sentence> existingSentences = new Select()
                .from(Sentence.class)
                .where(
                        String.format("sentence_id in (%s)", StringUtils.repeat("? ", sentences.size()).trim().replace(" ", ",")),
                        ids.toArray(new String[ids.size()]))
                .execute();

        Map<String, Sentence> existingSentencesMap = new HashMap<>();
        for (Sentence model : existingSentences) {
            existingSentencesMap.put(model.getSentenceId(), model);
        }

        ActiveAndroid.beginTransaction();
        try {
            for (Sentence sentence : sentences) {
                if (existingSentencesMap.containsKey(sentence.getSentenceId())) {
                    Sentence existingSentence = existingSentencesMap.get(sentence.getSentenceId());
                    existingSentence.setTargetSentence(sentence.getTargetSentence());
                    existingSentence.setKnownSentence(sentence.getKnownSentence());
                    existingSentence.setComplexity(sentence.getComplexity());
                    existingSentence.save();
                } else {
                    sentence.save();
                }
            }
            ActiveAndroid.setTransactionSuccessful();
        } finally {
            ActiveAndroid.endTransaction();
        }
    }

    public List<SentenceCollection> getCollections() {
        return new Select()
                .from(SentenceCollection.class)
                .orderBy("-done_count, target_lang, known_lang")
                .execute();
    }

    public List<Language> getLanguages() {
        return new Select()
                .from(Language.class)
                .execute();
    }

    public SentenceCollection getCollection(String collectionId) {
        if (StringUtils.isEmpty(collectionId)) {
            return null;
        }

        return new Select()
                .from(SentenceCollection.class)
                .where("collection_id = ?", collectionId)
                .executeSingle();
    }

    public Language getLanguage(String languageID) {
        List<Language> res = new Select()
                .from(Language.class)
                .where("language_id = ?", languageID)
                .limit(1)
                .execute();
        if (res.size() == 0) {
            return null;
        }
        return res.get(0);
    }

    public SentenceCollection reloadCollectionCounter(SentenceCollection collection) {
        DBG.todo("Threads");

        int rows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ?",
                new String[] {collection.getCollectionID()});
        int todoRows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ? and status = ?",
                new String[] {collection.getCollectionID(), String.valueOf(SentenceStatus.TODO.getStatus())});
        int againRows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ? and status = ?",
                new String[] {collection.getCollectionID(), String.valueOf(SentenceStatus.REPEAT.getStatus())});
        int doneRows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ? and status = ?",
                new String[] {collection.getCollectionID(), String.valueOf(SentenceStatus.DONE.getStatus())});
        int ignoreRows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ? and status = ?",
                new String[] {collection.getCollectionID(), String.valueOf(SentenceStatus.IGNORE.getStatus())});
        int skippedRows = SQLiteUtils.intQuery(
                "select count(*) from sentence where collection_id = ? and status = ?",
                new String[] {collection.getCollectionID(), String.valueOf(SentenceStatus.SKIPPED.getStatus())});
        int annotationCount = SQLiteUtils.intQuery(
                "select count(*) from annotation where collection_id = ?",
                new String[] {collection.getCollectionID()});

        if (todoRows > SentenceCollection.MAX_SENTENCES) {
            todoRows = SentenceCollection.MAX_SENTENCES;
        }
        todoRows = todoRows - doneRows - skippedRows;

        collection.count = rows;
        collection.todoCount = todoRows;
        collection.repeatCount = againRows;
        collection.doneCount = doneRows;
        collection.ignoreCount = ignoreRows;
        collection.annotationCount = annotationCount;
        collection.skippedCount = skippedRows;
        collection.save();

        return collection;
    }

    public List<Sentence> getRandomSentences(SentenceCollection collection) {
        return new Select()
                .from(Sentence.class)
                .where("collection_id = ?", collection.collectionID)
                .orderBy("random()")
                .limit(100)
                .execute();
    }

    public void removeCollectionSentences(SentenceCollection collection) {
        new Delete()
                .from(Sentence.class)
                .where("collection_id=?", collection.getCollectionID())
                .execute();
        reloadCollectionCounter(collection);
    }

    public Map<String, Language> getLanguagesByLanguageID() {
        HashMap<String, Language> res = new HashMap<>();
        for (Language language : getLanguages()) {
            res.put(language.getLanguageId(), language);
        }
        return res;
    }

    public Sentence getSentenceBySentenceId(String sentenceId) {
        return new Select()
                .from(Sentence.class)
                .where("sentence_id = ?", sentenceId)
                .executeSingle();
    }

    public From getSentencesByCollection(String collectionId, String filter) {
        From res = new Select()
                .from(Sentence.class)
                .where("collection_id=?", collectionId);
        if (!StringUtils.isEmpty(filter)) {
            SqlFilterUtils.addFilter(res, new String[]{"known", "target"}, filter);
        }
        res.orderBy("complexity");
        return res;
    }

    public From getSentencesByCollectionAndStatus(String collectionId, int sentenceStatus, String filter) {
        From res = new Select()
                .from(Sentence.class)
                .where("(collection_id=? and status=?)", collectionId, sentenceStatus);
        if (!StringUtils.isEmpty(filter)) {
            SqlFilterUtils.addFilter(res, new String[]{"known", "target"}, filter);
        }
        res.orderBy("complexity");
        return res;
    }

    public SentenceHistory getLatestSentenceHistory() {
        return new Select()
                .from(SentenceHistory.class)
                .orderBy("created desc")
                .executeSingle();
    }
} public class AnnotationService {
    private final Dao dao;

    public AnnotationService(Dao dao) {
        this.dao = dao;
    }

    public void addWordToAnnotation(Annotation annotation, String word) {
        if (StringUtils.isEmpty(annotation.collectionId)) {
            throw new Error("No collection");
        }

        word = String.valueOf(word).toLowerCase();

        if (annotation.getId() == null) {
            annotation.created = System.currentTimeMillis();
            annotation.save();
        }
        WordAnnotation wa = new WordAnnotation(word, annotation.getId());
        wa.collectionId = annotation.collectionId;
        wa.save();

        reloadGeneratedFields(annotation);
    }

    public void removeWordToAnnotation(Annotation annotation, WordAnnotation word) {
        word.delete();
        reloadGeneratedFields(annotation);
    }

    public void delete(Annotation annotation) {
        new Delete()
                .from(WordAnnotation.class)
                .where("annotation_id=?", annotation.getId())
                .execute();
        annotation.delete();
    }

    public void reloadGeneratedFields(Annotation annotation) {
        List<WordAnnotation> words = new Select()
                .from(WordAnnotation.class)
                .where("annotation_id=?", annotation.getId())
                .execute();

        Collections.sort(words, new Comparator<WordAnnotation>() {
            @Override
            public int compare(WordAnnotation m1, WordAnnotation m2) {
                return StringUtils.compare(m1.word, m2.word);
            }
        });

        StringBuilder wordList = new StringBuilder();
        for (WordAnnotation word : words) {
            if (wordList.length() > 0) {
                wordList.append(", ");
            }
            wordList.append(String.valueOf(word.word).toLowerCase());
        }

        for (WordAnnotation word : words) {
            word.words = wordList.toString();
            word.annotation = annotation.annotation;
        }

        annotation.words = wordList.toString();
        annotation.updated = System.currentTimeMillis();

        ActiveAndroid.beginTransaction();
        try {
            annotation.save();
            for (WordAnnotation word : words) {
                word.save();
            }
            ActiveAndroid.setTransactionSuccessful();
        } finally {
            ActiveAndroid.endTransaction();
        }
    }

    public List<Annotation> findAnnotations(String collectionId, String word) {
        List<WordAnnotation> wordAnnotations = new Select()
                .from(WordAnnotation.class)
                .where("word=? and collection_id=?", String.valueOf(word).toLowerCase(), collectionId)
                .execute();
        ArrayList<Annotation> annotations = new ArrayList<>();
        for (WordAnnotation wordAnnotation : wordAnnotations) {
            Annotation annotation = Annotation.load(Annotation.class, wordAnnotation.annotationId);
            if (annotation != null) {
                annotations.add(annotation);
            }
        }
        return annotations;
    }

    public From getAnnotationsSelectByCollectionAndFilter(String collectionId, String text) {
        String likeFilter = SqlFilterUtils.prepareLikeFilter(text);
        return new Select()
                .from(Annotation.class)
                .where("collection_id=? and (annotation like ? or annotation like ?)", collectionId, likeFilter.toString() + "%", "% " + likeFilter + "%")
                .orderBy("updated desc");
    }

    public From getAnnotationsSelectBydFilter(String text, String collectionId) {
        From res = new Select()
                .from(Annotation.class);
        SqlFilterUtils.addFilter(res, new String[] {"annotation"}, text);
        if (!StringUtils.isEmpty(collectionId)) {
            res.and("collection_id=?", collectionId);
        }
        res.orderBy("updated desc");
        return res;
    }

} public final class SentenceCollectionsService {

    private static final String TAG = SentenceCollectionsService.class.getSimpleName();

    private static final Random RANDOM = new SecureRandom(String.valueOf(System.currentTimeMillis()).getBytes());
    public static final int SUCCESS_STREAK_FOR_SKIPPING = 10;

    private final Dao dao;

    public SentenceCollectionsService(Dao dao) {
        this.dao = dao;
    }

    public Sentence nextSentence(Context context, SentenceCollection collection, String exceptSentenceId) {
        int maxRepeat = Preferences.getMaxRepeat(context);
        int status = SentenceStatus.TODO.getStatus();
        if (RANDOM.nextInt(maxRepeat) < collection.repeatCount) {
            // New sentence:
            status = SentenceStatus.REPEAT.getStatus();
        }

        Sentence result = getRandomSentenceByStatus(collection, SentenceStatus.fromStatus(status), exceptSentenceId);
        if (result == null) {
            return getRandomSentenceByStatus(collection, SentenceStatus.TODO, exceptSentenceId);
        }

        return result;
    }

    public Sentence getRandomKnownSentence(Context context, SentenceCollection collection, String exceptSentenceId) {
        return  new Select()
                .from(Sentence.class)
                .where("collection_id=? and status=? and sentence_id<>?", collection.collectionID, SentenceStatus.DONE.getStatus(), String.valueOf(exceptSentenceId))
                .orderBy("random()")
                .executeSingle();
    }

    private Sentence getRandomSentenceByStatus(SentenceCollection collection, SentenceStatus status, String exceptSentenceId) {
        List<Sentence> sentences = new Select()
                .from(Sentence.class)
                .where("collection_id=? and status=? and sentence_id!=?", collection.getCollectionID(), status.getStatus(), String.valueOf(exceptSentenceId))
                .orderBy("complexity")
                .limit(20)
                .execute();

        if (status == SentenceStatus.REPEAT) {
            // REPEAT sentences are special because we don't want them to be repeated immediately after
            // the user failed to guess them. So, find the oldest one:
            Map<String, Long> sentenceTakenTime = new HashMap<>();
            List<SentenceHistory> senHist = new Select()
                    .from(SentenceHistory.class)
                    .orderBy("created")
                    .limit(200)
                    .execute();
            for (SentenceHistory sh : senHist) {
                sentenceTakenTime.put(sh.sentenceId, sh.created);
            }

            for (Sentence sentence : sentences) {
                Long timeTaken = sentenceTakenTime.get(sentence.sentenceId);
                if (timeTaken == null) { // Longer than the sample of history we took => OK:
                    return sentence;
                }

                if (System.currentTimeMillis() - timeTaken.longValue() > TimeUnit.MINUTES.toMillis(5)) {
                    return sentence;
                }
            }
        }

        if (sentences.size() == 0) {
            return null;
        }
        return sentences.get(RANDOM.nextInt(sentences.size()));
    }

    public void updateStatus(final Sentence sentence, final SentenceStatus status, final long started, final long finished) {
        sentence.status = status.getStatus();
        sentence.save();

        new AsyncTask<String, Void, Void>() {
            @Override
            protected Void doInBackground(String... strings) {
                SentenceCollection collection = dao.reloadCollectionCounter(dao.getCollection(sentence.collectionId));

                SentenceHistory h = new SentenceHistory();
                h.sentenceId = sentence.getSentenceId();
                h.collectionId = sentence.collectionId;
                h.status = status.getStatus();
                h.created = finished;
                h.time = (int) (finished - started);
                h.doneCount = collection.doneCount;
                h.todoCount = collection.todoCount;
                h.repeatCount = collection.repeatCount;
                h.ignoreCount = collection.ignoreCount;
                h.save();

                return null;
            }
        }.execute();
    }

    public Sentence findPreviousSentence(String collectionId) {
        SentenceHistory hist = new Select()
                .from(SentenceHistory.class)
                .where("collection_id=?", collectionId)
                .orderBy("created desc")
                .executeSingle();
        if (hist == null) {
            return null;
        }

        return new Select()
                .from(Sentence.class)
                .where("sentence_id=?", hist.sentenceId)
                .executeSingle();
    }

    /**
     * Update counters after calling this.
     */
    public void updateStatusByComplexity(String collectionId, int limit, SentenceStatus fromStatus, SentenceStatus toStatus, String complecityOrder) {
        List<Sentence> sentences = new Select()
                .from(Sentence.class)
                .where("collection_id=? and status=?", collectionId, fromStatus.getStatus())
                .orderBy("complexity " + complecityOrder)
                .limit(limit)
                .execute();

        if (sentences == null || sentences.size() == 0) {
            return;
        }

        String[] sentenceIds = new String[sentences.size()];
        for (int i = 0; i < sentences.size(); i++) {
            sentenceIds[i] = sentences.get(i).sentenceId;
        }
        
        new Update(Sentence.class)
                .set("status=?", toStatus.getStatus())
                .where("sentence_id in (" + StringUtils.repeat(",?", sentences.size()).substring(1) + ")", sentenceIds)
                .execute();
    }

    /**
     * @see #updateStatusByComplexity(String, int, SentenceStatus, SentenceStatus, String)
     */
    public void skipSentences(String collectionId, int limit) {
        updateStatusByComplexity(collectionId, Math.abs(limit), SentenceStatus.TODO, SentenceStatus.SKIPPED, "asc");
    }

    /**
     * @see #updateStatusByComplexity(String, int, SentenceStatus, SentenceStatus, String)
     */
    public void unskipSentences(String collectionId, int limit) {
        updateStatusByComplexity(collectionId, Math.abs(limit), SentenceStatus.SKIPPED, SentenceStatus.TODO, "desc");
    }

    public boolean isCandidateForSkipping(String collectionId) {
        List<SentenceHistory> hist = new Select()
                .from(SentenceHistory.class)
                .where("collection_id=?", collectionId)
                .orderBy("created desc")
                .limit(SUCCESS_STREAK_FOR_SKIPPING)
                .execute();

        if (hist.size() < SUCCESS_STREAK_FOR_SKIPPING) {
            return false;
        }

        Set<String> sentenceIds = new HashSet<>();
        for (SentenceHistory h : hist) {
            if (sentenceIds.contains(h.sentenceId)) {
                return false;
            }
            sentenceIds.add(h.sentenceId);

            if (h.status != SentenceStatus.DONE.getStatus()) { // If any sentence not DONE => nope
                return false;
            }

            if (h.previousStatus == SentenceStatus.DONE.getStatus() && h.status == SentenceStatus.DONE.getStatus()) {
                // this was a practice of known sentences => not counting it:
                return false;
            }
        }

        return true;
    }

} @Table(name = "annotation")
public class Annotation extends Model {
    @Column(name = "annotation")
    public String annotation;

    @Column(name = "collection_id")
    public String collectionId;

    /**
     * This is regenerated every time {@link WordAnnotation} adds/removes a new word for this annotation.
     */
    @Column(name = "words")
    public String words;

    @Column(name = "created", index = true)
    public long created;

    @Column(name = "updated", index = true)
    public long updated;
} @Table(name = "language")
public class Language extends Model {
    @Column(name = "language_id", index = true, unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    public String languageId;

    @Column(name = "family")
    public String family;

    @Column(name = "name")
    public String name;

    @Column(name = "native_name")
    public String nativeName;

    @Column(name = "rtl")
    boolean rightToLeft;

    public String formatNativeName(String delimiter) {
        String[] parts;
        if (nativeName.contains(",")) {
            parts = StringUtils.capitalize(nativeName.split(",")[0]).split("\\s+");
        } else {
            parts = StringUtils.capitalize(nativeName).split("\\s+");
        }
        return StringUtils.join(parts, delimiter);
    }

    public String formatNameAndNativeName() {
        if (StringUtils.equals(name, nativeName)) {
            return name;
        }
        return name + " / " + nativeName;
    }

    public String getLanguageId() {
        return languageId;
    }

    public Language setLanguageId(String languageId) {
        this.languageId = languageId;
        return this;
    }

    public String getFamily() {
        return family;
    }

    public Language setFamily(String family) {
        this.family = family;
        return this;
    }

    public String getName() {
        return name;
    }

    public Language setName(String name) {
        this.name = name;
        return this;
    }

    public String getNativeName() {
        return nativeName;
    }

    public Language setNativeName(String nativeName) {
        this.nativeName = nativeName;
        return this;
    }

    public boolean isRightToLeft() {
        return rightToLeft;
    }

    public Language setRightToLeft(boolean rightToLeft) {
        this.rightToLeft = rightToLeft;
        return this;
    }
} @Table(name = "sentence")
public class Sentence extends Model {

    @Column(name = "sentence_id", index = true, unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    public String sentenceId;

    @Column(name = "collection_id", index = true)
    public String collectionId;

    @Column(name = "known")
    public String knownSentence;

    @Column(name = "target")
    public String targetSentence;

    @Column(name = "status")
    public int status = SentenceStatus.TODO.getStatus();

    @Column(name = "complexity", index = true)
    float complexity;

    public String[] getKnownSentences() {
        String[] res = String.valueOf(knownSentence).split("\\|");
        if (res.length == 1) {
            return res;
        }
        for (int i = 0; i < res.length; i++) {
            res[i] += String.format(" [%d/%d]", i+1, res.length);
        }
        return res;
    }

    public String getFirstKnownSentence() {
        return getKnownSentences()[0];
    }

    public SentenceStatus getSentenceStatus() {
        return SentenceStatus.fromStatus(status);
    }

    public String getSentenceId() {
        return sentenceId;
    }

    public Sentence setSentenceId(String sentenceId) {
        this.sentenceId = sentenceId;
        return this;
    }

    public String getCollectionId() {
        return collectionId;
    }

    public Sentence setCollectionId(String collectionId) {
        this.collectionId = collectionId;
        return this;
    }

    public String getKnownSentence() {
        return knownSentence;
    }

    public Sentence setKnownSentence(String knownSentence) {
        this.knownSentence = knownSentence;
        return this;
    }

    public String getTargetSentence() {
        return targetSentence;
    }

    public Sentence setTargetSentence(String targetSentence) {
        this.targetSentence = targetSentence;
        return this;
    }

    public int getStatus() {
        return status;
    }

    public Sentence setStatus(int status) {
        this.status = status;
        return this;
    }

    public float getComplexity() {
        return complexity;
    }

    public Sentence setComplexity(float complexity) {
        this.complexity = complexity;
        return this;
    }
} @Table(name = "sentence_collection")
public class SentenceCollection extends Model {

    private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.US);
    public static final int MAX_SENTENCES = 10_000;

    @Column(name = "collection_id", index = true, unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    public String collectionID;

    @Column(name = "known_lang")
    public String knownLanguage;

    @Column(name = "target_lang")
    public String targetLanguage;

    @Column(name = "filename")
    public String filename;

    @Column(name = "count")
    public int count;

    @Column(name = "todo_count")
    public int todoCount;

    @Column(name = "repeat_count")
    public int repeatCount;

    @Column(name = "done_count")
    public int doneCount;

    @Column(name = "ignore_count")
    public int ignoreCount;

    @Column(name = "skipped_count")
    public int skippedCount;

    @Column(name = "annotation_count")
    public int annotationCount;

    @Column(name="type")
    public CollectionType type;

    public boolean isDownloaded() {
        return count > 0;
    }

    public String formatDoneCount() {
        return formatCount(doneCount);
    }

    public String formatTodoCount() {
        return formatCount(todoCount);
    }

    public String formatIgnoreCount() {
        return formatCount(ignoreCount);
    }

    public String formatSkippedCount() {
        return formatCount(skippedCount);
    }

    public String formatRepeatCount() {
        return formatCount(repeatCount);
    }

    public String formatAnnotationCount() {
        return formatCount(annotationCount);
    }

    private String formatCount(int count) {
        if (count <= MAX_SENTENCES) {
            return NUMBER_FORMAT.format(count);
        }
        return NUMBER_FORMAT.format(MAX_SENTENCES) + "+";
    }

    public String getCollectionID() {
        return collectionID;
    }

    public SentenceCollection setCollectionID(String collectionID) {
        this.collectionID = collectionID;
        return this;
    }

    public String getKnownLanguage() {
        return knownLanguage;
    }

    public SentenceCollection setKnownLanguage(String knownLanguage) {
        this.knownLanguage = knownLanguage;
        return this;
    }

    public String getTargetLanguage() {
        return targetLanguage;
    }

    public SentenceCollection setTargetLanguage(String targetLanguage) {
        this.targetLanguage = targetLanguage;
        return this;
    }

    public String getFilename() {
        return filename;
    }

    public SentenceCollection setFilename(String filename) {
        this.filename = filename;
        return this;
    }

    public int getCount() {
        return count;
    }

    public SentenceCollection setCount(int count) {
        this.count = count;
        return this;
    }

    public int getTodoCount() {
        return todoCount;
    }

    public SentenceCollection setTodoCount(int todoCount) {
        this.todoCount = todoCount;
        return this;
    }

    public int getRepeatCount() {
        return repeatCount;
    }

    public SentenceCollection setRepeatCount(int repeatCount) {
        this.repeatCount = repeatCount;
        return this;
    }

    public int getDoneCount() {
        return doneCount;
    }

    public SentenceCollection setDoneCount(int doneCount) {
        this.doneCount = doneCount;
        return this;
    }

    public int getIgnoreCount() {
        return ignoreCount;
    }

    public SentenceCollection setIgnoreCount(int ignoreCount) {
        this.ignoreCount = ignoreCount;
        return this;
    }

    public int getSkippedCount() {
        return skippedCount;
    }

    public SentenceCollection setSkippedCount(int skippedCount) {
        this.skippedCount = skippedCount;
        return this;
    }

    public int getAnnotationCount() {
        return annotationCount;
    }

    public SentenceCollection setAnnotationCount(int annotationCount) {
        this.annotationCount = annotationCount;
        return this;
    }

    public CollectionType getType() {
        return type;
    }

    public SentenceCollection setType(CollectionType type) {
        this.type = type;
        return this;
    }
} public enum SentenceStatus {

    TODO(0, R.string.todo, R.string.todo_desc, android.R.color.primary_text_light),
    REPEAT(1, R.string.retry_again, R.string.retry_again_desc, R.color.error),
    DONE(2, R.string.done, R.string.done_desc, R.color.active),
    SKIPPED(3, R.string.skippped, R.string.skipped_desc, R.color.skipped),
    IGNORE(100, R.string.ignored, R.string.ignored_desc, R.color.inactive),

    ;

    private final int status;

    private final int nameResId;
    private final int descriptionResId;

    private final int color;

    SentenceStatus(int status, int nameResId, int descriptionResId, int color) {
        this.status = status;
        this.nameResId = nameResId;
        this.descriptionResId = descriptionResId;
        this.color = color;
    }

    public static SentenceStatus fromStatus(int status) {
        for (SentenceStatus sentenceStatus : values()) {
            if (sentenceStatus.getStatus() == status) {
                return sentenceStatus;
            }
        }
        return IGNORE;
    }

    public int getStatus() {
        return status;
    }

    public int getNameResId() {
        return nameResId;
    }

    public int getDescriptionResId() {
        return descriptionResId;
    }

    public int getColor() {
        return color;
    }
} @Data
@Accessors(chain = true)
@ToString
@Table(name = "word_annotation")
public class WordAnnotation extends Model {

    public WordAnnotation() {
        super();
    }

    public WordAnnotation(String word, long annotationId) {
        super();
        this.wordAnnotationId = word + "|" + annotationId;
        this.word = word;
        this.annotationId = annotationId;
    }

    @Column(name = "word_annotation_id", index = true, unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    public String wordAnnotationId;

    @Column(name = "word")
    public String word;

    @Column(name = "annotation_id")
    public long annotationId;

    @Column(name = "collection_id")
    public String collectionId;

    /**
     * Generated from {@link Annotation}
     */
    @Column(name = "annotation")
    public String annotation;

    /**
     * Generated from {@link Annotation}
     */
    @Column(name = "words")
    public String words;

} public class ShareUtils {
    private ShareUtils() throws Exception {
        throw new Exception();
    }

    /**
     * Share with other applications (but tries to put applications with "Translate" in the package
     * name in the first place(s).
     */
    public static void shareWithTranslate(Activity activity, String text) {
        Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND);
        sendIntent.setType("text/plain");
        sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, text);

        PackageManager pm = activity.getPackageManager();
        List<ResolveInfo> resInfo = pm.queryIntentActivities(sendIntent, 0);
        List<LabeledIntent> intentList = new ArrayList<>();
        for (ResolveInfo ri : resInfo) {
            if(ri.activityInfo.packageName.contains("translate")) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name));
                intent.putExtra(android.content.Intent.EXTRA_TEXT, text);
                intent.setAction(Intent.ACTION_SEND);
                intent.setType("text/plain");
                intentList.add(new LabeledIntent(intent, ri.activityInfo.packageName, ri.loadLabel(pm), ri.icon));
            }
        }
        LabeledIntent[] extraIntents = intentList.toArray( new LabeledIntent[ intentList.size() ]);

        Intent openInChooser = Intent.createChooser(sendIntent, "Translate");

        openInChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents);
        activity.startActivity(openInChooser);
    }

    /**
     * This method is important for users who have Google Translate "Tap to translate" enabled.
     */
    public static void copyToClipboard(Activity activity, String string) {
        ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clip = ClipData.newPlainText("translate", string);
        clipboard.setPrimaryClip(clip);
    }
} public final class SleepUtils {
    public SleepUtils() throws Exception {
        throw new Exception();
    }

    public static void disableSleep(Activity activity) {
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
/*        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "tag");
        wl.acquire();
        return wl;*/
/*        if (wl != null) {
            wl.release();
        }*/
    }

    public static void enableSleep(Activity activity) {
        activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

} public class Speech {

    private static final String TAG = Speech.class.getSimpleName();

    private TextToSpeech tts;

    private final Context context;
    private final Locale locale;
    private final boolean languageFound;
    private final boolean enabled;

    private final Set<Integer> toastMessagesShown = new HashSet<>();

    @Getter
    private boolean initialized = false;

    public Speech(Context context, Language language) {
        this.context = context;
        this.locale = findLocale(language);
        this.languageFound = locale != null;
        this.enabled = Preferences.isUseTTS(context);
        if (!this.enabled) {
            return;
        }
        tts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int i) {
                Speech.this.initialized = true;
            }
        });
        tts.setPitch(1);
        tts.setSpeechRate(0.75F);
        tts.setLanguage(locale);
    }

    private static Locale findLocale(Language language) {
        return findLocale(language.languageId);
    }

    private static Locale findLocale(String languageID) {
        for (Locale locale : Locale.getAvailableLocales()) {
            if (org.apache.commons.lang3.StringUtils.isEmpty(locale.getCountry()) && org.apache.commons.lang3.StringUtils.equals(locale.getLanguage(), languageID)) {
                return locale;
            }
        }
        for (Locale locale : Locale.getAvailableLocales()) {
            if (org.apache.commons.lang3.StringUtils.equals(locale.getLanguage(), languageID)) {
                return locale;
            }
        }
        return null;
    }

    public void speech(String speech) {
        if (!enabled) {
            return;
        }
        if (!languageFound) {
            showToastOnce(R.string.tts_language_not_available);
            return;
        }
        if (!initialized) {
            showToastOnce(R.string.tts_not_inititialized);
            return;
        }

/*        Bundle bundle = new Bundle();
        bundle.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, 1F);
        tts.speak(speech, TextToSpeech.QUEUE_FLUSH, bundle, null);*/
        tts.setLanguage(locale);
        tts.speak(speech, TextToSpeech.QUEUE_FLUSH, null);
    }

    private void showToastOnce(int resId) {
        if (toastMessagesShown.contains(resId)) {
            return;
        }
        toastMessagesShown.add(resId);
        Toast.makeText(context, resId, Toast.LENGTH_SHORT).show();
    }

    public void shutdown() {
        if (tts != null) {
            tts.shutdown();
        }
    }

    public static void main(String[] args) {
        System.out.println(findLocale("de"));
    }

} public final class TranslateUtils {

    private static final String TAG = TranslateUtils.class.getSimpleName();

    private TranslateUtils() throws Exception {
        throw new Exception();
    }

    public static void translate(Activity activity, String text) {

        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_PROCESS_TEXT);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_PROCESS_TEXT, text);

        String packageName = getTranslatePackageName(activity, intent);
        if (org.apache.commons.lang3.StringUtils.isEmpty(packageName)) {
            fallback(activity, text);
            return;
        }
        intent.setPackage(packageName);

        try {
            activity.startActivity(intent);
        } catch (Throwable e) {
            Log.e(TAG, e.getMessage(), e);
            fallback(activity, text);
        }
    }

    private static void fallback(Activity activity, String text) {
        Toast.makeText(activity, R.string.not_translate_app_found, Toast.LENGTH_SHORT).show();
        // If the user has "Tap to translate enabled", at least that will work:
        ShareUtils.copyToClipboard(activity, text);
    }

    private static String getTranslatePackageName(Activity activity, Intent intent) {
        return "com.google.android.apps.translate";
/*        PackageManager pm = activity.getPackageManager();
        List<ResolveInfo> resInfo = pm.queryIntentActivities(intent, 0);
        List<LabeledIntent> intentList = new ArrayList<>();
        for (ResolveInfo ri : resInfo) {
            if(ri.activityInfo.packageName.contains("com.google.android") && ri.activityInfo.packageName.endsWith(".translate")) {
                return ri.activityInfo.packageName;
            }
        }
        return null;*/
    }
} @ToString
public class WordChunk {
    public String chunk;
    public String word; // (only letters)

    public WordChunk() {
        this.chunk = "";
        this.word = "";
    }

    public WordChunk(String chunk, String word) {
        this.chunk = chunk;
        this.word = word;
    }
} public final class WordChunkUtils {

    private static final String SENTENCE_ENDING_INTERPUNCTIONS = ".?!";

    private WordChunkUtils() throws Exception {
        throw new Exception();
    }

    public static List<WordChunk> getWordChunks(String string) {
        string = string.replaceAll("\\s+", " ");

        for (char c : SENTENCE_ENDING_INTERPUNCTIONS.toCharArray()) {
            string = string.replace(" " + c, "" + c);
        }

        ArrayList<WordChunk> res = new ArrayList<>();
        String[] parts = string.split("\\s+");

        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            String word = getWord(part);
            if (res.size() > 0 && StringUtils.isEmpty(word)) {
                res.get(res.size() - 1).chunk += " " + part;
            } else {
                res.add(new WordChunk(part, word));
            }
        }

        WordChunk firstChunk = res.get(0);
        if (res.size() > 1 && StringUtils.isEmpty(firstChunk.word)) {
            res.remove(0);
            res.get(0).chunk = firstChunk.chunk + " " + res.get(0).chunk;
        }

        return res;
    }

    public static String getWord(String str) {
        int firstCharPos = -1;
        int lastCharPos = -1;
        for (int i = 0; i < str.toCharArray().length; i++) {
            char ch = str.charAt(i);
            if (Character.isLetter(ch) || Character.isDigit(ch)) {
                if (firstCharPos < 0) {
                    firstCharPos = i;
                }
                lastCharPos = i;
            }
        }

        if (firstCharPos >= 0) {
            return str.substring(firstCharPos, lastCharPos + 1);
        }

        return "";
    }

    public static void main(String[] args) {
        System.out.println(getWordChunks("ovo... je ,samo. test"));
    }

}
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.media.AudioManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.activeandroid.query.Select;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import info.puzz.a10000sentences.Application;
import info.puzz.a10000sentences.Constants;
import info.puzz.a10000sentences.R;
import info.puzz.a10000sentences.dao.Dao;
import info.puzz.a10000sentences.databinding.ActivitySentenceQuizBinding;
import info.puzz.a10000sentences.logic.AnnotationService;
import info.puzz.a10000sentences.logic.SentenceCollectionsService;
import info.puzz.a10000sentences.models.Annotation;
import info.puzz.a10000sentences.models.Language;
import info.puzz.a10000sentences.models.Sentence;
import info.puzz.a10000sentences.models.SentenceCollection;
import info.puzz.a10000sentences.models.SentenceStatus;
import info.puzz.a10000sentences.models.WordAnnotation;
import info.puzz.a10000sentences.utils.ShareUtils;
import info.puzz.a10000sentences.utils.SleepUtils;
import info.puzz.a10000sentences.utils.Speech;
import info.puzz.a10000sentences.utils.TranslateUtils;
import info.puzz.a10000sentences.utils.WordChunk;
import info.puzz.a10000sentences.utils.WordChunkUtils;
package info.puzz.a10000sentences.activities;






public class SentenceQuizActivity extends BaseActivity {

    private static final String TAG = SentenceQuizActivity.class.getSimpleName();

    public static enum Type {
        ONLY_KNOWN,
        KNOWN_AND_UNKNOWN,
        RETURN_BACK,
    }

    private static final String ARG_SENTENCE_ID = "arg_sentence_id";
    private static final String ARG_TYPE = "arg_type";

    @Inject Dao dao;
    @Inject SentenceCollectionsService sentenceCollectionsService;
    @Inject AnnotationService annotationService;

    private long started;
    //private boolean skipSentenceCandidate;

    ActivitySentenceQuizBinding binding;

    private Button[] answerButtons;
    private Integer originalButtonColor;
    private Speech speech;
    private Language targetLanguage;

    private Type type;
    private String sentenceId;

    public static <T extends BaseActivity> void startSentence(T activity, String sentenceId, Type type) {
        if (activity.getClass().equals(SentenceQuizActivity.class)) {
            // if already in a quiz, just replace the current activity on the stack:
            activity.finish();
        }
        Intent intent = new Intent(activity, SentenceQuizActivity.class)
                .putExtra(ARG_SENTENCE_ID, sentenceId)
                .putExtra(ARG_TYPE, type);
        activity.startActivity(intent);
    }

    public static <T extends BaseActivity> void startRandom(T activity, Dao dao, SentenceCollectionsService sentenceCollectionsService, String collectionId, Type type, String exceptSentenceId) {
        SentenceCollection collection = dao.getCollection(collectionId);

        Sentence sentence;
        if (type == Type.ONLY_KNOWN) {
            sentence = sentenceCollectionsService.getRandomKnownSentence(activity, collection, exceptSentenceId);
        } else {
            sentence = sentenceCollectionsService.nextSentence(activity, collection, exceptSentenceId);
        }

        if (sentence == null) {
            Toast.makeText(activity, activity.getString(R.string.no_sentence_found), Toast.LENGTH_SHORT).show();
            return;
        }

        startSentence(activity, sentence.sentenceId, type);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Application.COMPONENT.injectActivity(this);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_sentence_quiz);
        //FontAwesomeIcons.fa_volume_up

        type = (Type) getIntent().getSerializableExtra(ARG_TYPE);
        sentenceId = getIntent().getStringExtra(ARG_SENTENCE_ID);

        Sentence sentence = dao.getSentenceBySentenceId(sentenceId);
        if (sentence == null) {
            Toast.makeText(this, R.string.unexpected_error, Toast.LENGTH_SHORT).show();
            CollectionsActivity.start(this);
            return;
        }

        SentenceCollection collection = dao.getCollection(sentence.collectionId);
        List<Sentence> randomSentences = dao.getRandomSentences(collection);

        targetLanguage = dao.getLanguage(collection.targetLanguage);
        setTitle(targetLanguage.name);

        binding.setQuiz(new SentenceQuiz(sentence, 4, randomSentences));
        binding.setCollection(collection);

        if (targetLanguage.isRightToLeft()) {
            binding.targetSentence.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
        }

        binding.sentenceStatus.setTextColor(ContextCompat.getColor(this, binding.getQuiz().getSentence().getSentenceStatus().getColor()));

        answerButtons = new Button[] { binding.answer1, binding.answer2, binding.answer3, binding.answer4, };
        setupAnswerButtons();

        binding.startQuiz.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                binding.startQuizGroup.setVisibility(View.GONE);
                binding.quizSentenceGroup.setVisibility(View.VISIBLE);
                binding.quizButtons.setVisibility(View.VISIBLE);
                binding.knownSentence.setTextColor(R.color.inactive);
            }
        });
        binding.knownSentence.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                binding.getQuiz().nextKnownSentenceAlternative();
            }
        });

        adjustFontSize();
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }

    private void setupAnswerButtons() {
        for (final Button answerButton : answerButtons) {
            answerButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    submitResponse(answerButton, answerButton.getText().toString());
                }
            });
            answerButton.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    String text = answerButton.getText().toString();
                    TranslateUtils.translate(SentenceQuizActivity.this, text);