public class Card implements Parcelable {

    private static final String ADVANCED_POSTFIX = " (Advanced)";

    private static final String RANDOM_ID = "Random";

    // Minimum number of advanced games counted before we will use it
    private static final int ADVANCED_COUNT_MINIMUM = 50;

    public static final Card RANDOM_HERO =
        new Card(Type.HERO, RANDOM_ID, R.string.card_random_hero, 0);

    public static final Card RANDOM_VILLAIN =
        new Card(Type.VILLAIN, RANDOM_ID, R.string.card_random_villain, 0);

    public static final Card RANDOM_ENVIRONMENT =
        new Card(Type.ENVIRONMENT, RANDOM_ID, R.string.card_random_environment, 0);

    public static enum Type {
        HERO,
        VILLAIN,
        ENVIRONMENT,
    }

    private Type mType;

    private String mId;

    private int mNameResId;

    private int mIconResId;

    private int mPoints;

    private boolean mEnabled;

    private boolean mAdvanced;

    private int mAdvancedPoints;

    // If this is an advanced card, this is the # of advanced games logged
    // with the card.  We want to cut off a low # because of how unreliable
    // the data can be.
    private int mAdvancedCount;

    /**
     * Some "cards" are actually a set of cards, i.e. the Vengeance Five.
     */
    private List<Card> mTeam;

    public Card() {
        // Default constructor
    }

    public Card(Type type, String id, int nameResId, int points) {
        mType = type;
        mId = id;
        mNameResId = nameResId;
        mPoints = points;
        mEnabled = true;
    }

    public Card(Card other) {
        mType = other.mType;
        mId = other.mId;
        mNameResId = other.mNameResId;
        mIconResId = other.mIconResId;
        mPoints = other.mPoints;
        mEnabled = other.mEnabled;
        mAdvanced = other.mAdvanced;
        mAdvancedPoints = other.mAdvancedPoints;
        mAdvancedCount = other.mAdvancedCount;
    }

    public Type getType() {
        return mType;
    }

    public void setType(Type type) {
        mType = type;
    }

    public String getId() {
        return mId;
    }

    public void setId(String id) {
        mId = id;
    }

    public void setNameResId(int resId) {
        mNameResId = resId;
    }

    public int getNameResId() {
        return mNameResId;
    }

    public CharSequence getName(Context context) {
        CharSequence name = context.getString(mNameResId);
        return mAdvanced ? context.getString(R.string.advanced_TEMPLATE, name) : name;
    }

    public void setIconResId(int resId) {
        mIconResId = resId;
    }

    public int getIconResId() {
        return mIconResId;
    }

    public int getPoints() {
        return mPoints;
    }

    public void setPoints(int points) {
        mPoints = points;
    }

    public boolean isEnabled() {
        return mEnabled && (!isAdvanced() || canBeAdvanced());
    }

    public void setEnabled(boolean enabled) {
        mEnabled = enabled;
    }

    public boolean isAdvanced() {
        return mAdvanced;
    }

    public void makeAdvanced() {
        mAdvanced = true;
        mId = getAdvancedId();
    }

    public void setAdvancedPoints(int advancedPoints) {
        mAdvancedPoints = advancedPoints;
    }

    public int getAdvancedPoints() {
        if (mAdvancedPoints == 0) {
            return mPoints;
        }
        return mAdvancedPoints;
    }

    public void setAdvancedCount(int count) {
        mAdvancedCount = count;
    }

    public int getAdvancedCount() {
        return mAdvancedCount;
    }

    public boolean canBeAdvanced() {
        return mAdvancedCount >= ADVANCED_COUNT_MINIMUM && Prefs.isAdvancedAllowed();
    }

    public void addTeamMember(Card teamMember) {
        if (mTeam == null) {
            mTeam = new ArrayList<Card>();
        }
        mTeam.add(teamMember);
    }

    public boolean isTeam() {
        return mTeam != null;
    }

    public List<Card> getTeamMembers() {
        return mTeam;
    }

    public boolean isRandom() {
        return mId.equals(RANDOM_ID) || isTeam();
    }

    /**
     * @return the advanced version of this card
     */
    public String getAdvancedId() {
        if (mId.endsWith(ADVANCED_POSTFIX)) {
            return mId;
        }
        else {
            return mId + ADVANCED_POSTFIX;
        }
    }

    @Override
    public int hashCode() {
        return mId.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Card)) {
            return false;
        }

        Card other = (Card) o;
        return mId.equals(other.mId);
    }

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

    //////////////////////////////////////////////////////////////////////////
    // Comparators

    public static final Comparator<Card> POINT_COMPARATOR = new Comparator<Card>() {
        @Override
        public int compare(Card lhs, Card rhs) {
            return lhs.getPoints() - rhs.getPoints();
        }
    };

    public static final Comparator<Card> POINT_ADVANCED_COMPARATOR = new Comparator<Card>() {
        @Override
        public int compare(Card lhs, Card rhs) {
            return lhs.getAdvancedPoints() - rhs.getAdvancedPoints();
        }
    };

    public static Comparator<Card> getNameComparator(final Context context) {
        return new Comparator<Card>() {
            public int compare(Card lhs, Card rhs) {
                String lhName = context.getString(lhs.getNameResId());
                String rhName = context.getString(rhs.getNameResId());
                int comp = lhName.compareTo(rhName);

                // When comparing names, advanced always comes below the
                // non-advanced version of the card.
                if (comp == 0) {
                    if (lhs.isAdvanced()) {
                        return 1;
                    }
                    else if (rhs.isAdvanced()) {
                        return -1;
                    }
                }

                return comp;
            }
        };
    }

    //////////////////////////////////////////////////////////////////////////
    // Parcelable

    private Card(Parcel in) {
        boolean hasType = in.readByte() == 1;
        if (hasType) {
            mType = Type.values()[in.readInt()];
        }
        mId = in.readString();
        mNameResId = in.readInt();
        mIconResId = in.readInt();
        mPoints = in.readInt();
        mEnabled = in.readByte() == 1;
        mAdvanced = in.readByte() == 1;
        mAdvancedPoints = in.readInt();
        mAdvancedCount = in.readInt();
        mTeam = in.readArrayList(getClass().getClassLoader());
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (mType != null) {
            dest.writeByte((byte) 1);
            dest.writeInt(mType.ordinal());
        }
        else {
            dest.writeByte((byte) 0);
        }
        dest.writeString(mId);
        dest.writeInt(mNameResId);
        dest.writeInt(mIconResId);
        dest.writeInt(mPoints);
        dest.writeByte((byte) (mEnabled ? 1 : 0));
        dest.writeByte((byte) (mAdvanced ? 1 : 0));
        dest.writeInt(mAdvancedPoints);
        dest.writeInt(mAdvancedCount);
        dest.writeList(mTeam);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<Card> CREATOR = new Parcelable.Creator<Card>() {
        public Card createFromParcel(Parcel in) {
            return new Card(in);
        }

        public Card[] newArray(int size) {
            return new Card[size];
        }
    };

} public enum Difficulty {

    RANDOM(-1, R.string.difficulty_random),
    EASY(90, R.string.difficulty_easy),
    MEDIUM(75, R.string.difficulty_medium),
    HARD(50, R.string.difficulty_hard),
    VERY_HARD(30, R.string.difficulty_very_hard),
    GOOD_LUCK(15, R.string.difficulty_good_luck),
    PICK_YOUR_OWN(-1, R.string.difficulty_specify);

    private int mTargetWinPercent;
    private int mStrResId;

    private Difficulty(int targetWinPercent, int strResId) {
        mTargetWinPercent = targetWinPercent;
        mStrResId = strResId;
    }

    public int getTargetWinPercent() {
        return mTargetWinPercent;
    }

    public int getStrResId() {
        return mStrResId;
    }
} public class GameSetup implements Parcelable {

    private static final int MIN_HEROES = 3;
    private static final int MAX_HEROES = 5;

    private List<Card> mHeroes = new ArrayList<Card>(5);

    private Card mVillain;
    private List<Card> mVillainTeam = new ArrayList<Card>(1);

    private Card mEnvironment;

    public GameSetup() {
        // Default setup is 3 random heroes, 1 random villain and 1 random environment
        mHeroes.add(Card.RANDOM_HERO);
        mHeroes.add(Card.RANDOM_HERO);
        mHeroes.add(Card.RANDOM_HERO);
        mVillain = Card.RANDOM_VILLAIN;
        mEnvironment = Card.RANDOM_ENVIRONMENT;
    }

    public GameSetup(GameSetup toCopy) {
        mHeroes.addAll(toCopy.mHeroes);
        mVillain = toCopy.mVillain;
        mEnvironment = toCopy.mEnvironment;
    }

    public void reset() {
        for (int a = 0; a < mHeroes.size(); a++) {
            mHeroes.set(a, Card.RANDOM_HERO);
        }
        mVillain = Card.RANDOM_VILLAIN;
        mEnvironment = Card.RANDOM_ENVIRONMENT;
        mVillainTeam.clear();
    }

    public List<Card> getHeroes() {
        return mHeroes;
    }

    public int getHeroCount() {
        return mHeroes.size();
    }

    public void addHero() {
        mVillainTeam.clear();
        mHeroes.add(Card.RANDOM_HERO);
    }

    public void setHero(int index, Card card) {
        mHeroes.set(index, card);
    }

    public void removeHero(int index) {
        mVillainTeam.clear();
        mHeroes.remove(index);
    }

    public boolean canRemoveHero() {
        return mHeroes.size() > MIN_HEROES;
    }

    public boolean canAddHero() {
        return mHeroes.size() < MAX_HEROES;
    }

    public void setVillain(Card card) {
        mVillainTeam.clear();
        mVillain = card;
    }

    public Card getVillain() {
        return mVillain;
    }

    public Card getVillainAt(int position) {
        if (mVillainTeam.size() == 0) {
            return mVillain;
        }

        return mVillainTeam.get(position);
    }

    public void setVillainTeam(List<Card> villainTeam) {
        mVillainTeam = villainTeam;
    }

    public List<Card> getVillainTeam() {
        return mVillainTeam;
    }

    public int getVillainCount() {
        return Math.max(mVillainTeam.size(), 1);
    }

    public boolean isAdvancedVillain() {
        return mVillain.isAdvanced();
    }

    public void setEnvironment(Card card) {
        mEnvironment = card;
    }

    public Card getEnvironment() {
        return mEnvironment;
    }

    /**
     * @return true if there is a random card in the setup, false if all cards are filled out
     */
    public boolean hasRandomCards() {
        for (Card card : mHeroes) {
            if (card.isRandom()) {
                return true;
            }
        }

        // Villain is either random with no team, or is random but hasn't selected a team
        if (mVillain.isRandom() && (!mVillain.isTeam() || mVillainTeam.size() == 0)) {
            return true;
        }

        return mEnvironment.isRandom();
    }

    public boolean isCompletelyRandom() {
        for (Card card : mHeroes) {
            if (!card.equals(Card.RANDOM_HERO)) {
                return false;
            }
        }

        return mVillain.equals(Card.RANDOM_VILLAIN) && mEnvironment.equals(Card.RANDOM_ENVIRONMENT);
    }

    /**
     * We might not be able to randomize if there aren't enough options available
     */
    public boolean canRandomize() {
        return getFirstLackingType() == null;
    }

    /**
     * @return the first type without enough enabled cards, or null if we're good to go
     */
    public Type getFirstLackingType() {
        // Make sure we count each hero (and alternate) only once, as we
        // cannot pick the same hero twice (even with promos)
        int heroCount = 0;
        List<Card> heroList = Db.getCards(Type.HERO);
        Set<Card> countedSet = new HashSet<Card>();
        for (Card hero : heroList) {
            if (!countedSet.contains(hero)) {
                heroCount++;
                countedSet.addAll(Db.getCardAndAlternates(hero));
            }
        }

        if (heroCount < getHeroCount()) {
            return Type.HERO;
        }
        else if (Db.getCards(Type.VILLAIN).size() == 0) {
            return Type.VILLAIN;
        }
        else if (Db.getCards(Type.ENVIRONMENT).size() == 0) {
            return Type.ENVIRONMENT;
        }

        return null;
    }

    /**
     * Calculates the points that this setup represents.  If there
     * are random cards, it counts them as 0.
     */
    public int getPoints() {
        int points = 0;

        for (Card card : mHeroes) {
            if (card != Card.RANDOM_HERO) {
                points += card.getPoints();
            }
        }

        if (mVillain != Card.RANDOM_VILLAIN) {
            if (isAdvancedVillain()) {
                points += mVillain.getAdvancedPoints();
            }
            else {
                points += mVillain.getPoints();
            }
        }

        if (mEnvironment != Card.RANDOM_ENVIRONMENT) {
            points += mEnvironment.getPoints();
        }

        // Factor in # of players
        points += Db.getPointsForNumPlayers(mHeroes.size());

        return points;
    }

    /**
     * Gets the possible range of points (if all cards were filled in with
     * their highest/lowest scoring items).
     *
     * If there are no random cards, the range is 0.
     */
    public Pair<Integer, Integer> getPointRange() {
        return new Pair<Integer, Integer>(getPoints(true), getPoints(false));
    }

    // Returns either the minimum or maximum points possible with this game setup.
    private int getPoints(boolean minPoints) {
        int points = getPoints();

        List<Card> possibleHeroes = Db.getCards(Type.HERO);
        Collections.sort(possibleHeroes, Card.POINT_COMPARATOR);
        for (Card hero : mHeroes) {
            if (hero.isRandom()) {
                if (minPoints) {
                    points += possibleHeroes.get(0).getPoints();
                    possibleHeroes.remove(0);
                }
                else {
                    points += possibleHeroes.get(possibleHeroes.size() - 1).getPoints();
                    possibleHeroes.remove(possibleHeroes.size() - 1);
                }
            }
        }

        if (mVillain.isRandom()) {
            List<Card> possibleVillains = Db.getCards(Type.VILLAIN);
            Collections.sort(possibleVillains,
                Prefs.isAdvancedAllowed() ? Card.POINT_ADVANCED_COMPARATOR : Card.POINT_COMPARATOR);

            if (minPoints) {
                points += possibleVillains.get(0).getPoints();
            }
            else {
                points += possibleVillains.get(possibleVillains.size() - 1).getPoints();
            }
        }

        if (mEnvironment.isRandom()) {
            List<Card> possibleEnvironments = Db.getCards(Type.ENVIRONMENT);
            Collections.sort(possibleEnvironments, Card.POINT_COMPARATOR);

            if (minPoints) {
                points += possibleEnvironments.get(0).getPoints();
            }
            else {
                points += possibleEnvironments.get(possibleEnvironments.size() - 1).getPoints();
            }
        }

        return points;
    }

    /**
     * @return The estimated chance of winning, as an int 0-100
     */
    public int getWinPercent() {
        return Db.getWinPercent(getPoints());
    }

    public void updateFrom(GameSetup other) {
        mHeroes.clear();
        mHeroes.addAll(other.mHeroes);
        mVillain = other.mVillain;
        mEnvironment = other.mEnvironment;
        mVillainTeam.clear();
        mVillainTeam.addAll(other.mVillainTeam);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof GameSetup)) {
            return false;
        }

        GameSetup other = (GameSetup) o;
        return mHeroes.equals(other.mHeroes) && mVillain.equals(other.mVillain)
            && mEnvironment.equals(other.mEnvironment);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Heroes: ");
        int heroCount = mHeroes.size();
        for (int a = 0; a < heroCount; a++) {
            if (a != 0) {
                sb.append(", ");
            }
            sb.append(mHeroes.get(a).getId());
        }

        sb.append("\nVillain: " + mVillain.getId());
        if (isAdvancedVillain()) {
            sb.append(" (Advanced)");
        }

        if (mVillainTeam.size() != 0) {
            List<String> teamIds = new ArrayList<String>();
            for (Card teamMember : mVillainTeam) {
                teamIds.add(teamMember.getId());
            }

            sb.append("\nVillain Team): [" + TextUtils.join(", ", teamIds) + "]");
        }

        sb.append("\nEnvironment: " + mEnvironment.getId());

        return sb.toString();
    }

    //////////////////////////////////////////////////////////////////////////
    // Parcelable

    private GameSetup(Parcel in) {
        ClassLoader cl = getClass().getClassLoader();
        in.readList(mHeroes, cl);
        mVillain = in.readParcelable(cl);
        mEnvironment = in.readParcelable(cl);
        in.readList(mVillainTeam, cl);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeList(mHeroes);
        dest.writeParcelable(mVillain, flags);
        dest.writeParcelable(mEnvironment, flags);
        dest.writeList(mVillainTeam);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<GameSetup> CREATOR = new Parcelable.Creator<GameSetup>() {
        public GameSetup createFromParcel(Parcel in) {
            return new GameSetup(in);
        }

        public GameSetup[] newArray(int size) {
            return new GameSetup[size];
        }
    };

} public class Prefs {

    private static final String PREF_ADVANCED_ALLOWED = "PREF_ADVANCED_ALLOWED";

    private static Context sAppContext;

    public static void init(Context context) {
        sAppContext = context.getApplicationContext();
    }

    public static boolean isAdvancedAllowed() {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(sAppContext);
        return prefs.getBoolean(PREF_ADVANCED_ALLOWED, false);
    }

    public static void setAdvancedAllowed(boolean allowed) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(sAppContext);
        prefs.edit().putBoolean(PREF_ADVANCED_ALLOWED, allowed).apply();
    }

} public class AboutDialogFragment extends DialogFragment {

    public static final String TAG = AboutDialogFragment.class.getName();

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        builder.setTitle(R.string.title_about);

        StringBuilder sb = new StringBuilder();
        sb.append(getString(R.string.about_app));
        sb.append("<br /><br />");
        sb.append(getString(R.string.about_explanation));
        sb.append("<br /><br />");
        sb.append(getString(R.string.about_tracker));
        sb.append("<br /><br />");
        sb.append(getString(R.string.credit_points));

        return new AlertDialog.Builder(getActivity())
            .setMessage(Html.fromHtml(sb.toString()))
            .setNeutralButton(R.string.ok, null)
            .create();
    }

    @Override
    public void onResume() {
        super.onResume();

        ((TextView) getDialog().findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
    }

} public interface CardPickerDialogFragmentListener {

    public void onCardSelected(Card card);
} public interface DifficultyDialogFragmentListener {

    public void onDifficultyChosen(Difficulty difficulty);
} public class RandomizerListFragment extends ListFragment implements GameSetupAdapterListener,
    CardPickerDialogFragmentListener, DifficultyDialogFragmentListener, SpecifyDifficultyDialogFragmentListener {

    public static final String TAG = RandomizerListFragment.class.getName();

    private static final String INSTANCE_SELECTED_CARD_TYPE = "INSTANCE_SELECTED_CARD_TYPE";
    private static final String INSTANCE_SELECTED_CARD_INDEX = "INSTANCE_SELECTED_CARD_INDEX";
    private static final String INSTANCE_GAME_SETUP = "INSTANCE_GAME_SETUP";
    private static final String INSTANCE_BASE_GAME_SETUP = "INSTANCE_BASE_GAME_SETUP";
    private static final String INSTANCE_TARGET_WIN_PERCENT = "INSTANCE_TARGET_WIN_PERCENT";

    private RandomizerListFragmentListener mListener;

    private GameSetupAdapter mAdapter;

    private GameSetup mGameSetup;

    // We keep the base GameSetup around, in case we want to randomize multiple times in a row
    private int mTargetWinPercent;
    private GameSetup mBaseGameSetup;

    // Which index we're currently selecting for the card dialog fragment
    private Type mSelectCardType;
    private int mSelectCardIndex;

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

        setHasOptionsMenu(true);

        if (savedInstanceState == null) {
            mGameSetup = new GameSetup();
        }
        else {
            mGameSetup = savedInstanceState.getParcelable(INSTANCE_GAME_SETUP);

            if (savedInstanceState.containsKey(INSTANCE_BASE_GAME_SETUP)) {
                mBaseGameSetup = savedInstanceState.getParcelable(INSTANCE_BASE_GAME_SETUP);
                mTargetWinPercent = savedInstanceState.getInt(INSTANCE_TARGET_WIN_PERCENT);
            }

            if (savedInstanceState.containsKey(INSTANCE_SELECTED_CARD_TYPE)) {
                mSelectCardType = Type.values()[savedInstanceState.getInt(INSTANCE_SELECTED_CARD_TYPE)];
                mSelectCardIndex = savedInstanceState.getInt(INSTANCE_SELECTED_CARD_INDEX);
            }
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        mListener = (RandomizerListFragmentListener) activity;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mAdapter = new GameSetupAdapter(getActivity(), mGameSetup, this);
        setListAdapter(mAdapter);
    }

    @Override
    public void onResume() {
        super.onResume();

        // Check that all selected cards are still enabled
        // (May have been disabled in card config activity)
        validateGameSetup();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putParcelable(INSTANCE_GAME_SETUP, mGameSetup);

        if (mBaseGameSetup != null) {
            outState.putParcelable(INSTANCE_BASE_GAME_SETUP, mBaseGameSetup);
            outState.putInt(INSTANCE_TARGET_WIN_PERCENT, mTargetWinPercent);
        }

        if (mSelectCardType != null) {
            outState.putInt(INSTANCE_SELECTED_CARD_TYPE, mSelectCardType.ordinal());
        }
        outState.putInt(INSTANCE_SELECTED_CARD_INDEX, mSelectCardIndex);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        mSelectCardType = mAdapter.getType(position);

        // Check that we even have something to select from
        if (Db.getCards(mSelectCardType).size() == 0) {
            DialogFragment df = NotEnoughCardsDialogFragment.newInstance(mSelectCardType);
            df.show(getFragmentManager(), NotEnoughCardsDialogFragment.TAG);
        }
        else {
            int start = mAdapter.getTypeStart(mSelectCardType);
            mSelectCardIndex = position - start - 1;
            Card card = (Card) mAdapter.getItem(position);

            CardPickerDialogFragment dialogFragment = CardPickerDialogFragment.newInstance(mSelectCardType, mGameSetup,
                card);
            dialogFragment.show(getFragmentManager(), CardPickerDialogFragment.TAG);
        }
    }

    public GameSetup getGameSetup() {
        return mGameSetup;
    }

    /**
     * Indicate that some outside settings may have changed and invalidated the game setup; if so, we want
     * to change things so we are still legit .
     */
    public void validateGameSetup() {
        boolean hasChanged = false;
        List<Card> heroes = mGameSetup.getHeroes();
        for (int a = 0; a < heroes.size(); a++) {
            if (!heroes.get(a).isEnabled()) {
                mGameSetup.setHero(a, Card.RANDOM_HERO);
                hasChanged = true;
            }
        }
        if (!mGameSetup.getVillain().isEnabled()) {
            mGameSetup.setVillain(Card.RANDOM_VILLAIN);
            hasChanged = true;
        }
        if (!mGameSetup.getEnvironment().isEnabled()) {
            mGameSetup.setEnvironment(Card.RANDOM_ENVIRONMENT);
            hasChanged = true;
        }

        if (hasChanged) {
            onGameSetupChanged();
        }
    }

    public void launchRandomizerDialog(boolean fromStats) {
        if (!mGameSetup.canRandomize()) {
            if (fromStats) {
                startActivity(new Intent(getActivity(), CardConfigActivity.class));
            }
            else {
                DialogFragment df = NotEnoughCardsDialogFragment.newInstance(mGameSetup.getFirstLackingType());
                df.show(getFragmentManager(), NotEnoughCardsDialogFragment.TAG);
            }
        }
        else {
            DifficultyDialogFragment df = new DifficultyDialogFragment();
            df.show(getFragmentManager(), DifficultyDialogFragment.TAG);
        }
    }

    private void onGameSetupChanged() {
        onGameSetupChanged(null);
    }

    private void onGameSetupChanged(GameSetup gameSetup) {
        mBaseGameSetup = gameSetup;
        mAdapter.notifyDataSetChanged();
        mListener.onGameSetupChanged(mGameSetup);
        getActivity().invalidateOptionsMenu();
    }

    private void randomize(int targetWinPercent) {
        GameSetup baseGameSetup = mBaseGameSetup;
        if (baseGameSetup == null) {
            baseGameSetup = new GameSetup(mGameSetup);
        }

        Randomizer randomizer = new Randomizer(baseGameSetup);
        if (baseGameSetup.canRandomize()) {
            if (targetWinPercent == Difficulty.RANDOM.getTargetWinPercent()) {
                mGameSetup.updateFrom(randomizer.randomize());
            }
            else {
                mGameSetup.updateFrom(randomizer.randomize(targetWinPercent));
            }

            onGameSetupChanged(baseGameSetup);
            mTargetWinPercent = targetWinPercent;
        }
        else {
            DialogFragment df = NotEnoughCardsDialogFragment.newInstance(baseGameSetup.getFirstLackingType());
            df.show(getFragmentManager(), NotEnoughCardsDialogFragment.TAG);
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Action bar

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.fragment_randomizer, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        menu.findItem(R.id.action_reroll).setVisible(mBaseGameSetup != null);
        menu.findItem(R.id.action_randomize).setVisible(mBaseGameSetup == null && mGameSetup.hasRandomCards());
        menu.findItem(R.id.action_reset).setVisible(!mGameSetup.isCompletelyRandom());
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_randomize:
                launchRandomizerDialog(false);
                return true;
            case R.id.action_reroll:
                randomize(mTargetWinPercent);
                return true;
            case R.id.action_reset:
                mGameSetup.reset();
                onGameSetupChanged();
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    //////////////////////////////////////////////////////////////////////////
    // GameSetupAdapterListener

    @Override
    public void onAdd(Type type) {
        mGameSetup.addHero();
        onGameSetupChanged();
    }

    @Override
    public void onRemove(Type type, int index) {
        mGameSetup.removeHero(index);
        onGameSetupChanged();
    }

    //////////////////////////////////////////////////////////////////////////
    // CardPickerDialogFragmentListener

    @Override
    public void onCardSelected(Card card) {
        switch (mSelectCardType) {
            case HERO:
                mGameSetup.setHero(mSelectCardIndex, card);
                break;
            case VILLAIN:
                mGameSetup.setVillain(card);
                break;
            case ENVIRONMENT:
                mGameSetup.setEnvironment(card);
                break;
        }

        onGameSetupChanged();
    }

    //////////////////////////////////////////////////////////////////////////
    // DifficultyDialogFragmentListener

    @Override
    public void onDifficultyChosen(Difficulty difficulty) {
        if (difficulty == Difficulty.PICK_YOUR_OWN) {
            SpecifyDifficultyDialogFragment df = new SpecifyDifficultyDialogFragment();
            df.show(getFragmentManager(), SpecifyDifficultyDialogFragment.TAG);
        }
        else {
            randomize(difficulty.getTargetWinPercent());
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // SpecifyDifficultyDialogFragmentListener

    @Override
    public void onSpecificDifficultyChosen(int targetWinPercent) {
        randomize(targetWinPercent);
    }

    //////////////////////////////////////////////////////////////////////////
    // Interface

    public interface RandomizerListFragmentListener {

        public void onGameSetupChanged(GameSetup gameSetup);
    }

} public interface RandomizerListFragmentListener {

    public void onGameSetupChanged(GameSetup gameSetup);
} public interface SpecifyDifficultyDialogFragmentListener {

    public void onSpecificDifficultyChosen(int targetWinPercent);
} public class StatsFragment extends Fragment {

    private StatsFragmentListener mListener;

    private TextView mStatsTextView;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        mListener = (StatsFragmentListener) activity;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_stats, container, false);

        mStatsTextView = Ui.findView(root, R.id.stats_text_view);

        root.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.onStatsClick();
            }
        });

        return root;
    }

    public void bind(GameSetup gameSetup) {
        String str;
        if (!gameSetup.canRandomize()) {
            str = getString(NotEnoughCardsDialogFragment.getErrorResId(gameSetup.getFirstLackingType()));
        }
        else {
            if (gameSetup.hasRandomCards()) {
                Pair<Integer, Integer> winPointRange = gameSetup.getPointRange();
                int low = Db.getWinPercent(winPointRange.second);
                int high = Db.getWinPercent(winPointRange.first);
                str = getString(R.string.template_win_range, low, high);
            }
            else {
                str = getString(R.string.template_win_chance, gameSetup.getWinPercent());
            }
        }

        mStatsTextView.setText(Html.fromHtml(str));
    }

    //////////////////////////////////////////////////////////////////////////
    // Listener

    public interface StatsFragmentListener {

        public void onStatsClick();
    }
} public interface StatsFragmentListener {

    public void onStatsClick();
} public class AccountUtils {

    public static final Uri SYNC_URI = Uri.parse("content://" + PointContentProvider.CONTENT_AUTHORITY + "/points");

    public static final String ACCOUNT_TYPE = "com.idunnolol.sotm";
    public static final String ACCOUNT_NAME = "Data Updater";

    private static final long SYNC_INTERVAL = 24 * 60 * 60;

    public static Account getSyncAccount() {
        return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
    }

    public static void addSyncAccount(Context context) {
        AccountManager manager = AccountManager.get(context);

        Account[] accounts = manager.getAccountsByType(ACCOUNT_TYPE);
        if (accounts.length != 0) {
            Log.d("Tried to add sync account, but one already existed!");
        }
        else {
            Log.i("Adding sync account...");

            Account account = getSyncAccount();
            manager.addAccountExplicitly(account, null, null);
            ContentResolver.setIsSyncable(account, PointContentProvider.CONTENT_AUTHORITY, 1);
            ContentResolver.setSyncAutomatically(account, PointContentProvider.CONTENT_AUTHORITY, true);
        }
    }

    public static void startPeriodicSync() {
        Log.i("Adding periodic syncs every " + SYNC_INTERVAL + " seconds...");
        ContentResolver.addPeriodicSync(getSyncAccount(), PointContentProvider.CONTENT_AUTHORITY, new Bundle(),
            SYNC_INTERVAL);
    }
}
import android.app.Activity;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import com.danlew.utils.Log;
import com.danlew.utils.Ui;
import com.idunnolol.sotm.R;
import com.idunnolol.sotm.data.Card;
import com.idunnolol.sotm.data.Difficulty;
import com.idunnolol.sotm.data.GameSetup;
import com.idunnolol.sotm.data.Prefs;
import com.idunnolol.sotm.fragment.AboutDialogFragment;
import com.idunnolol.sotm.fragment.CardPickerDialogFragment.CardPickerDialogFragmentListener;
import com.idunnolol.sotm.fragment.DifficultyDialogFragment.DifficultyDialogFragmentListener;
import com.idunnolol.sotm.fragment.RandomizerListFragment;
import com.idunnolol.sotm.fragment.RandomizerListFragment.RandomizerListFragmentListener;
import com.idunnolol.sotm.fragment.SpecifyDifficultyDialogFragment.SpecifyDifficultyDialogFragmentListener;
import com.idunnolol.sotm.fragment.StatsFragment;
import com.idunnolol.sotm.fragment.StatsFragment.StatsFragmentListener;
import com.idunnolol.sotm.sync.AccountUtils;
package com.idunnolol.sotm.activity;


public class RandomizerActivity extends Activity implements RandomizerListFragmentListener,
    CardPickerDialogFragmentListener, DifficultyDialogFragmentListener, SpecifyDifficultyDialogFragmentListener,
    StatsFragmentListener {

    private RandomizerListFragment mRandomizerListFragment;
    private StatsFragment mStatsFragment;

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

        setTitle(R.string.title_randomizer);

        setContentView(R.layout.activity_randomizer);

        mRandomizerListFragment = Ui.findFragment(this, R.id.randomizer_list_fragment);
        mStatsFragment = Ui.findFragment(this, R.id.stats_fragment);

        if (savedInstanceState == null) {
            // Make sure we have a sync account setup and that it's set to sync
            AccountUtils.addSyncAccount(this);