package dan.dit.whatsthat.riddle.achievement.holders;
import android.content.res.Resources;
import android.util.Log;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import dan.dit.whatsthat.R;
import dan.dit.whatsthat.achievement.Achievement;
import dan.dit.whatsthat.achievement.AchievementDataEvent;
import dan.dit.whatsthat.achievement.AchievementManager;
import dan.dit.whatsthat.achievement.AchievementProperties;
import dan.dit.whatsthat.riddle.achievement.AchievementDataRiddleGame;
import dan.dit.whatsthat.riddle.achievement.AchievementDataRiddleType;
import dan.dit.whatsthat.riddle.achievement.AchievementPropertiesMapped;
import dan.dit.whatsthat.riddle.achievement.DailyAchievement;
import dan.dit.whatsthat.riddle.types.PracticalRiddleType;
import dan.dit.whatsthat.testsubject.TestSubject;
/**
* Holds a bunch of achievements that are special in the case that they will be able to be
* achieved (and claimed) multiple times. Achieving is possible once a day, every achievement
* will be reset after initialization if not achieved on the same day and not yet reset on that day.
* If the achievement reward wasn't claimed yet, the reward will be discarded!<br>
* By default initialized not listening to any achievement data events. So onInit needs to
* add the achievement as a listener to the required data events. OnAchieved should as usual
* unregister those listeners.<br>
* Additionally only some daily achievements will be available per day, these will be chosen
* at random on initialization of the holder.
* Created by daniel on 12.01.16.
*/
public class DailyAchievementsHolder implements AchievementHolder {
private static final int MAX_ACHIEVEMENTS_PER_DAY = 2;
private Map<Integer, DailyAchievement> mAchievements;
private Calendar mToday;
public DailyAchievementsHolder() {
//no validation is done if this is really today's date. Would need to request date from
// some server, we trust the users or think if you find this "flaw" and want to abuse
// it.. well have fun ;)
mToday = Calendar.getInstance();
}
@Override
public void makeAchievements(AchievementManager manager) {
mAchievements = new TreeMap<>();
mAchievements.put(Achievement1.NUMBER, new Achievement1(manager));
mAchievements.put(Achievement2.NUMBER, new Achievement2(manager));
// ids of SingleType achievements are negative!
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
int number = -type.getId();
AchievementSingleType achievement = new AchievementSingleType(manager, type, 0,
number, -1);
mAchievements.put(number, achievement);
}
mAchievements.put(Achievement3.NUMBER, new Achievement3(manager));
}
private static class Achievement1 extends DailyAchievement {
private static final int REQUIRED_RIDDLES_TO_SOLVE = 5;//>1 for plural reasons
public static final int NUMBER = 1;
public static final int LEVEL = 0;
public static final int REWARD = 40;
public static final boolean DISCOVERED = true;
public static final String KEY_GAMES_SOLVED_COUNT = NUMBER +
"games_solved_today_count";
private AchievementPropertiesMapped<String> mMiscData;
public Achievement1(AchievementManager manager) {
super(R.string.achievement_daily_1_name, R.string.achievement_daily_1_descr, 0, manager,
LEVEL, NUMBER, REWARD, REQUIRED_RIDDLES_TO_SOLVE, DISCOVERED);
}
@Override
public CharSequence getDescription(Resources res) {
return res.getString(mDescrResId, REQUIRED_RIDDLES_TO_SOLVE);
}
@Override
public void onInit() {
super.onInit();
mMiscData = TestSubject.getInstance().getAchievementHolder().getMiscData();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementDataGame().addListener(this);
}
}
@Override
protected void onReset() {
mMiscData.putValue(KEY_GAMES_SOLVED_COUNT, 0L, AchievementProperties.UPDATE_POLICY_ALWAYS);
}
@Override
public void onAchieved() {
super.onAchieved();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementDataGame().removeListener(this);
}
}
@Override
public void onIsAvailableDataEvent(AchievementDataEvent event) {
if (event.getEventType() != AchievementDataEvent.EVENT_TYPE_DATA_CLOSE
|| !areDependenciesFulfilled()) {
return;
}
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
AchievementDataRiddleGame data = type.getAchievementDataGame();
if (data == event.getChangedData()) {
if (!data.isCustom() && data.isSolved() && mMiscData != null) {
mMiscData.increment(KEY_GAMES_SOLVED_COUNT, 1L, 0L);
achieveDelta(1);
}
break;
}
}
}
}
private static class Achievement2 extends DailyAchievement {
public static final int NUMBER = 2;
public static final int REWARD = 50;
public static final boolean DISCOVERED = true;
public static final String KEY_GAMES_SOLVED_FOR_TYPE_COUNT = NUMBER +
"games_solved_today_for_type_count";
private static final int REQUIRED_RIDDLES_TO_SOLVE_PER_TYPE = 1;
private static final int REQUIRED_DIFFERENT_TYPES = 4;
public static final int LEVEL = REQUIRED_DIFFERENT_TYPES - 2;
public Achievement2(AchievementManager manager) {
super(R.string.achievement_daily_2_name, R.string.achievement_daily_2_descr, 0, manager,
LEVEL, NUMBER, REWARD, REQUIRED_DIFFERENT_TYPES * REQUIRED_RIDDLES_TO_SOLVE_PER_TYPE,
DISCOVERED);
}
@Override
public CharSequence getDescription(Resources res) {
return res.getString(mDescrResId, REQUIRED_RIDDLES_TO_SOLVE_PER_TYPE, REQUIRED_DIFFERENT_TYPES);
}
@Override
protected void onReset() {
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementData(mManager).putValue(KEY_GAMES_SOLVED_FOR_TYPE_COUNT, 0L,
AchievementProperties.UPDATE_POLICY_ALWAYS);
}
}
@Override
public void onAchieved() {
super.onAchieved();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementData(mManager).removeListener(this);
}
}
@Override
public void onInit() {
super.onInit();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementData(mManager).addListener(this);
}
}
@Override
public void onIsAvailableDataEvent(AchievementDataEvent event) {
if (!event.hasChangedKey(AchievementDataRiddleType.KEY_GAMES_SOLVED)
|| !areDependenciesFulfilled()) {
return;
}
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
AchievementDataRiddleType data = type.getAchievementData(mManager);
if (data == event.getChangedData()) {
if (!data.isCustom()
&& data.getValue(KEY_GAMES_SOLVED_FOR_TYPE_COUNT, 0L) < REQUIRED_RIDDLES_TO_SOLVE_PER_TYPE) {
data.increment(KEY_GAMES_SOLVED_FOR_TYPE_COUNT, 1L, 0L);
achieveDelta(1);
}
break;
}
}
}
}
private static class AchievementSingleType extends DailyAchievement {
public static final int LEVEL = 0;
public static final int REWARD = 25;
public static final boolean DISCOVERED = true;
public static final String KEY_GAMES_SOLVED_FOR_SINGLE_TYPE_COUNT = "single_type" +
"games_solved_today_for_single_type_count";
private static final int REQUIRED_RIDDLES_TO_SOLVE_DEFAULT = 2;//>1 for plural reasons,else >0
private final PracticalRiddleType mType;
public AchievementSingleType(AchievementManager manager, PracticalRiddleType type,
int nameResId, int number, int solveCount) {
super(nameResId == 0 ? R.string.achievement_daily_single_type_default_name : nameResId,
R.string.achievement_daily_single_type_descr,
0, manager,
LEVEL, number, REWARD,
solveCount <= 1 ? REQUIRED_RIDDLES_TO_SOLVE_DEFAULT : solveCount,
DISCOVERED);
mType = type;
}
@Override
public int getIconResId() {
return mType.getIconResId();
}
@Override
public CharSequence getDescription(Resources res) {
return res.getString(mDescrResId, REQUIRED_RIDDLES_TO_SOLVE_DEFAULT,
res.getString(mType.getNameResId()));
}
@Override
protected void onReset() {
mType.getAchievementData(mManager).putValue(KEY_GAMES_SOLVED_FOR_SINGLE_TYPE_COUNT, 0L,
AchievementProperties.UPDATE_POLICY_ALWAYS);
}
@Override
public void onAchieved() {
super.onAchieved();
mType.getAchievementData(mManager).removeListener(this);
}
@Override
public void onInit() {
super.onInit();
mType.getAchievementData(mManager).addListener(this);
}
@Override
public void onIsAvailableDataEvent(AchievementDataEvent event) {
if (!event.hasChangedKey(AchievementDataRiddleType.KEY_GAMES_SOLVED)
|| !areDependenciesFulfilled()) {
return;
}
AchievementDataRiddleType data = mType.getAchievementData(mManager);
if (data == event.getChangedData()) {
if (!data.isCustom()) {
data.increment(KEY_GAMES_SOLVED_FOR_SINGLE_TYPE_COUNT, 1L, 0L);
achieveDelta(1);
}
}
}
@Override
public void setDependencies() {
super.setDependencies();
mDependencies.add(TestSubject.getInstance().getRiddleTypeDependency(mType));
}
}
private static class Achievement3 extends DailyAchievement {
public static final int NUMBER = 3;
public static final int LEVEL = 2;
public static final int REWARD = 70;
public static final boolean DISCOVERED = true;
private static final long MAX_SOLVING_TIME = 30000;//ms
public Achievement3(AchievementManager manager) {
super(R.string.achievement_daily_3_name, R.string.achievement_daily_3_descr, 0, manager,
LEVEL, NUMBER, REWARD, 1,
DISCOVERED);
}
@Override
public CharSequence getDescription(Resources res) {
return res.getString(mDescrResId, MAX_SOLVING_TIME / 1000L);
}
@Override
protected void onReset() {
}
@Override
public void onAchieved() {
super.onAchieved();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementDataGame().removeListener(this);
}
}
@Override
public void onInit() {
super.onInit();
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
type.getAchievementDataGame().addListener(this);
}
}
@Override
public void onIsAvailableDataEvent(AchievementDataEvent event) {
if (event.getEventType() != AchievementDataEvent.EVENT_TYPE_DATA_CLOSE
|| !areDependenciesFulfilled()) {
return;
}
for (PracticalRiddleType type : PracticalRiddleType.ALL_PLAYABLE_TYPES) {
AchievementDataRiddleGame data = type.getAchievementDataGame();
if (data == event.getChangedData()) {
if (!data.isCustom()
&& data.isSolved()
&& data.getValue(AchievementDataRiddleGame.KEY_PLAYED_TIME, 0L) <=
MAX_SOLVING_TIME) {
achieve();
}
break;
}
}
}
}
@Override
public void addDependencies() {
for (DailyAchievement achievement : mAchievements.values()) {
achievement.setDependencies();
}
}
@Override
public void initAchievements() {
for (DailyAchievement achievement : mAchievements.values()) {
achievement.init(mToday);
}
makeResetCandidatesAvailable();
}
private void makeResetCandidatesAvailable() {
int availableCount = 0;
List<DailyAchievement> candidates = new ArrayList<>(mAchievements.size());
for (DailyAchievement achievement : mAchievements.values()) {
if (achievement.gotResetToday(mToday) && !achievement.isAvailable()) {
candidates.add(achievement);
}
if (achievement.isAvailable()) {
availableCount++;
}
}
//by default when one is reset all others are reset too on this day
if (candidates.size() > 0) {
// now get only some random of these achievements and make them available
Collections.shuffle(candidates);
for (int i = 0;
i < Math.min(MAX_ACHIEVEMENTS_PER_DAY - availableCount, candidates.size());
i++) {
candidates.get(i).setAvailable();
}
}
}
public boolean refresh() {
mToday = Calendar.getInstance();
boolean foundAny = false;
for (DailyAchievement achievement : mAchievements.values()) {
if (achievement.checkedReset(mToday)) {
foundAny = true;
}
}
Log.d("Achievement", "Refreshing daily achievements, found any that got reset: " +
foundAny);
if (foundAny) {
makeResetCandidatesAvailable();
}
return foundAny;
}
@Override
public List<? extends Achievement> getAchievements() {
List<DailyAchievement> available = new ArrayList<>(mAchievements.size());
for (DailyAchievement achievement : mAchievements.values()) {
if (achievement.isAvailable()) {
available.add(achievement);
}
}
return available;
}
@Override
public int getExpectableTestSubjectScore(int testSubjectLevel) {
int expected = 0;
for (Achievement achievement : mAchievements.values()) {
expected += achievement.getExpectedScore(testSubjectLevel);
}
return expected;
}
}