/* * Copyright 2015 Daniel Dittmar * * 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 dan.dit.whatsthat.riddle; import android.content.Context; import android.graphics.Bitmap; import android.support.annotation.NonNull; import android.util.Log; import android.util.LruCache; import java.util.LinkedList; import java.util.List; import dan.dit.whatsthat.image.Image; import dan.dit.whatsthat.riddle.control.RiddleGame; import dan.dit.whatsthat.riddle.types.PracticalRiddleType; import dan.dit.whatsthat.testsubject.TestSubject; import dan.dit.whatsthat.util.general.ObserverController; import dan.dit.whatsthat.util.image.Dimension; /** * Class to keep track of all unsolved riddles and making new riddles by using the RiddleMaker class. Retrieve * an instance of the RiddleInitializer which needs to be initialized beforehand! * Created by daniel on 31.03.15. */ public class RiddleManager { private final List<Riddle> mAllUnsolvedRiddles = new LinkedList<>(); private static LruCache<Riddle, Bitmap> mMemoryCache; private ObserverController<UnsolvedRiddleListener, Void> mUnsolvedRiddleListenerController = new ObserverController<>(); private RiddleMaker mMaker; private int mSolvedRiddlesCount; public static void addToCache(Riddle riddle, Bitmap image) { if (riddle == null || image == null) { return; } if (mMemoryCache == null) { final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); final int cacheSize = maxMemory / 20; mMemoryCache = new LruCache<Riddle, Bitmap>(cacheSize) { @Override protected int sizeOf(Riddle key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getByteCount() / 1024; } }; } mMemoryCache.put(riddle, image); } public static Bitmap getFromCache(Riddle riddle) { if (riddle == null || mMemoryCache == null) { return null; } return mMemoryCache.get(riddle); } public void onRiddleInvalidated(Riddle riddle) { if (riddle != null) { if (mMemoryCache != null) { mMemoryCache.remove(riddle); } if (mAllUnsolvedRiddles.remove(riddle)) { mUnsolvedRiddleListenerController.notifyObservers(null); } } } /** * There was a change (not further specified) with the unsolved riddles. * This informs listeners to recheck the information they need from the manager. No data is * passed for the event, information needs to be pulled from observer. */ public interface UnsolvedRiddleListener extends ObserverController.Observer<Void> { } /** * Initializes the manager with a list of unsolved riddles. * @param loadedUnsolvedRiddles A non null list of riddles. */ RiddleManager(@NonNull List<Riddle> loadedUnsolvedRiddles) { mAllUnsolvedRiddles.addAll(loadedUnsolvedRiddles); } /** * Notifies the manager that the given riddle was solved, removing * it from unsolved riddles list. * @param riddle The riddle that was solved which must not be null. */ public void onRiddleSolved(@NonNull Riddle riddle) { //noinspection ConstantConditions if (riddle == null) { throw new IllegalArgumentException("On solved riddle with null riddle."); } mSolvedRiddlesCount++; TestSubject.getInstance().addSolvedRiddleScore(riddle.getScore()); RiddleInitializer.INSTANCE.registerUsedRiddleImage(riddle); if (mAllUnsolvedRiddles.remove(riddle)) { mUnsolvedRiddleListenerController.notifyObservers(null); } if (mMemoryCache != null) { mMemoryCache.remove(riddle); } Log.d("Riddle", "Solved riddles: " + mSolvedRiddlesCount + ", unsolved riddles " + mAllUnsolvedRiddles.size()); } /** * Retrieves the exact list of unsolved riddles. Changes are possible, * no listeners will be notified about them though! * @return The list of unsolved riddles. */ List<Riddle> getUnsolvedRiddles() { return mAllUnsolvedRiddles; } /** * Gets the amount of unsolved riddles. A riddle is also considered unsolved * if it is currently displayed as an InitializedRiddle. * @return The amount of unsolved riddles. */ public int getUnsolvedRiddleCount() { return mAllUnsolvedRiddles.size(); } /** * Notifies the manager about a new unsolved riddle and adds * it to the list of unsolved riddles. * @param riddle The unsolved riddle which must not be null. */ public void onUnsolvedRiddle(@NonNull Riddle riddle) { //noinspection ConstantConditions if (riddle == null) { throw new IllegalArgumentException("On unsolved riddle with null riddle."); } RiddleInitializer.INSTANCE.registerUsedRiddleImage(riddle); if (!mAllUnsolvedRiddles.contains(riddle)) { mAllUnsolvedRiddles.add(riddle); mUnsolvedRiddleListenerController.notifyObservers(null); } } /** * Registers a listener that is informed about changes to the unsolved riddles. * A listener cannot register multiple times. * @param listener The listener to register. */ public void registerUnsolvedRiddleListener(UnsolvedRiddleListener listener) { mUnsolvedRiddleListenerController.addObserver(listener); } /** * Removes the listener that previously registered to the manager. * @param listener The listener to remove. * @return True if the given listener was registered and removed. */ public boolean unregisterUnsolvedRiddleListener(UnsolvedRiddleListener listener) { return mUnsolvedRiddleListenerController.removeObserver(listener); } // ************ MAKING RIDDLES; DELEGATING AND MANAGING A RIDDLE MAKER ************************ /** * Checks if a RiddleMaker is currently running. * @return If a RiddleMaker is running. */ public boolean isMakingRiddle() { return mMaker != null && mMaker.isRunning(); } /** * Cancels the running RiddleMaker, does nothing if none is running. */ public void cancelMakeRiddle() { if (isMakingRiddle()) { mMaker.cancel(); } mMaker = null; } /** * Creates and starts a new RiddleMaker. Cancels previously running makers. * @see RiddleMaker#makeNew(android.content.Context, dan.dit.whatsthat.riddle.types.PracticalRiddleType, dan.dit.whatsthat.util.image.Dimension, int, dan.dit.whatsthat.riddle.RiddleMaker.RiddleMakerListener) */ public void makeRiddle(Context context, PracticalRiddleType type, Dimension maxCanvasDimension, int densityDpi, RiddleMaker.RiddleMakerListener listener) { cancelMakeRiddle(); mMaker = new RiddleMaker(); mMaker.makeNew(context, type, maxCanvasDimension, densityDpi, listener); } public void remakeCurrentWithNewType(final Context context, final Riddle current, PracticalRiddleType newType, Dimension maxCanvasDimension, int densityDpi, final RiddleMaker.RiddleMakerListener listener) { cancelMakeRiddle(); mMaker = new RiddleMaker(); mMaker.remakeCurrentWithNewType(context, current, newType, maxCanvasDimension, densityDpi, new RiddleMaker.RiddleMakerListener() { @Override public void onRiddleReady(RiddleGame riddle) { if (Riddle.deleteFromDatabase(context, current.getId())) { onRiddleInvalidated(current); } listener.onRiddleReady(riddle); } @Override public void onError(Image image, Riddle riddle) { listener.onError(image, riddle); } @Override public void onProgressUpdate(int progress) { listener.onProgressUpdate(progress); } }); } /** * Creates and starts a new RiddleMaker. Cancels previously running makers. * @see RiddleMaker#remakeOld(android.content.Context, long, dan.dit.whatsthat.util.image.Dimension, int, dan.dit.whatsthat.riddle.RiddleMaker.RiddleMakerListener) */ public void remakeOld(Context context, long suggestedId, Dimension maxCanvasDim, int screenDensity, RiddleMaker.RiddleMakerListener listener) { cancelMakeRiddle(); mMaker = new RiddleMaker(); mMaker.remakeOld(context, suggestedId, maxCanvasDim, screenDensity, listener); } /** * Creates and starts a new RiddleMaker. Cancels previously running makers. * @see RiddleMaker#makeSpecific(android.content.Context, dan.dit.whatsthat.image.Image, dan.dit.whatsthat.riddle.types.PracticalRiddleType, dan.dit.whatsthat.util.image.Dimension, int, dan.dit.whatsthat.riddle.RiddleMaker.RiddleMakerListener) */ public void makeSpecific(Context context, Image image, PracticalRiddleType type, Dimension maxCanvasDim, int screenDensity, RiddleMaker.RiddleMakerListener listener) { cancelMakeRiddle(); mMaker = new RiddleMaker(); mMaker.makeSpecific(context, image, type, maxCanvasDim, screenDensity, listener); } }