<commit_before>
package com.battlelancer.seriesguide.util;

import com.battlelancer.seriesguide.Constants;
import com.battlelancer.seriesguide.items.Series;
import com.battlelancer.seriesguide.provider.SeriesContract;
import com.battlelancer.seriesguide.provider.SeriesContract.EpisodeSearch;
import com.battlelancer.seriesguide.provider.SeriesContract.Episodes;
import com.battlelancer.seriesguide.provider.SeriesContract.Seasons;
import com.battlelancer.seriesguide.provider.SeriesContract.Shows;
import com.battlelancer.seriesguide.ui.SeriesGuidePreferences;
import com.battlelancer.seriesguide.ui.UpcomingFragment.UpcomingQuery;
import com.battlelancer.thetvdbapi.ImageCache;

import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.preference.PreferenceManager;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;

public class DBUtils {

    static final String TAG = "SeriesDatabase";

    /**
     * Use 9223372036854775807 (Long.MAX_VALUE) for unknown airtime/no next
     * episode so they will get sorted last.
     */
    public static final String UNKNOWN_NEXT_AIR_DATE = "9223372036854775807";

    /**
     * Looks up the episodes of a given season and stores the count of already
     * aired, but not watched ones in the seasons watchcount.
     * 
     * @param seasonid
     */
    public static void updateUnwatchedCount(Context context, String seasonid) {
        ContentResolver resolver = context.getContentResolver();
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);
        Uri episodesOfSeasonUri = Episodes.buildEpisodesOfSeasonUri(seasonid);

        // all a seasons episodes
        Cursor total = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, null, null, null);
        final int totalcount = total.getCount();
        total.close();

        // unwatched, aired episodes
        String selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + " like '%-%'"
                + " AND " + Episodes.FIRSTAIRED + "<=?";
        Cursor unwatched = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", today
        }, null);
        final int count = unwatched.getCount();
        unwatched.close();

        // unwatched, aired in the future episodes
        selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + ">?";
        Cursor unaired = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", today
        }, null);
        final int unaired_count = unaired.getCount();
        unaired.close();

        // unwatched, no airdate
        selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + "=?";
        Cursor noairdate = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", ""
        }, null);

        final int noairdate_count = noairdate.getCount();
        noairdate.close();

        ContentValues update = new ContentValues();
        update.put(Seasons.WATCHCOUNT, count);
        update.put(Seasons.UNAIREDCOUNT, unaired_count);
        update.put(Seasons.NOAIRDATECOUNT, noairdate_count);
        update.put(Seasons.TOTALCOUNT, totalcount);
        resolver.update(Seasons.buildSeasonUri(seasonid), update, null, null);
    }

    /**
     * Returns all episodes that air today or later. Using Pacific Time to
     * determine today. Excludes shows that are hidden.
     * 
     * @return Cursor including episodes with show title, network, airtime and
     *         posterpath.
     */
    public static Cursor getUpcomingEpisodes(Context context) {
        Calendar cal = Calendar.getInstance();
        // go an hour back in time, so episodes move to recent one hour late
        cal.add(Calendar.HOUR_OF_DAY, -1);
        final String recentThreshold = String.valueOf(cal.getTimeInMillis());
        String query = UpcomingQuery.QUERY_UPCOMING;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean isOnlyFavorites = prefs.getBoolean(SeriesGuidePreferences.KEY_ONLYFAVORITES, false);
        String[] selectionArgs;
        if (isOnlyFavorites) {
            query += UpcomingQuery.SELECTION_ONLYFAVORITES;
            selectionArgs = new String[] {
                    recentThreshold, "0", "1"
            };
        } else {
            selectionArgs = new String[] {
                    recentThreshold, "0"
            };
        }

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW,
                UpcomingQuery.PROJECTION, query, selectionArgs, UpcomingQuery.SORTING_UPCOMING);
    }

    /**
     * Return all episodes that aired the day before and earlier. Using Pacific
     * Time to determine today. Excludes shows that are hidden.
     * 
     * @param context
     * @return Cursor including episodes with show title, network, airtime and
     *         posterpath.
     */
    public static Cursor getRecentEpisodes(Context context) {
        Calendar cal = Calendar.getInstance();
        // go an hour back in time, so episodes move to recent one hour late
        cal.add(Calendar.HOUR_OF_DAY, -1);
        final String recentThreshold = String.valueOf(cal.getTimeInMillis());
        String query = UpcomingQuery.QUERY_RECENT;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean isOnlyFavorites = prefs.getBoolean(SeriesGuidePreferences.KEY_ONLYFAVORITES, false);
        String[] selectionArgs;
        if (isOnlyFavorites) {
            query += UpcomingQuery.SELECTION_ONLYFAVORITES;
            selectionArgs = new String[] {
                    recentThreshold, "0", "1"
            };
        } else {
            selectionArgs = new String[] {
                    recentThreshold, "0"
            };
        }

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW,
                UpcomingQuery.PROJECTION, query, selectionArgs, UpcomingQuery.SORTING_RECENT);
    }

    /**
     * Updates the given boolean to the EPISODE_WATCHED column of the given
     * episode row. Be aware that the database stores the value as an integer.
     * 
     * @param rowid
     * @param state
     */
    public static void markEpisode(Context context, String episodeId, boolean state) {
        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, state);

        context.getContentResolver()
                .update(Episodes.buildEpisodeUri(episodeId), values, null, null);
        context.getContentResolver().notifyChange(Episodes.CONTENT_URI, null);
    }

    /**
     * Marks the next episode (if there is one) of this show as watched.<commit_msg>Prevent null pointer when attempting to mark an episode.<commit_after>
package com.battlelancer.seriesguide.util;

import com.battlelancer.seriesguide.Constants;
import com.battlelancer.seriesguide.items.Series;
import com.battlelancer.seriesguide.provider.SeriesContract;
import com.battlelancer.seriesguide.provider.SeriesContract.EpisodeSearch;
import com.battlelancer.seriesguide.provider.SeriesContract.Episodes;
import com.battlelancer.seriesguide.provider.SeriesContract.Seasons;
import com.battlelancer.seriesguide.provider.SeriesContract.Shows;
import com.battlelancer.seriesguide.ui.SeriesGuidePreferences;
import com.battlelancer.seriesguide.ui.UpcomingFragment.UpcomingQuery;
import com.battlelancer.thetvdbapi.ImageCache;

import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.preference.PreferenceManager;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;

public class DBUtils {

    static final String TAG = "SeriesDatabase";

    /**
     * Use 9223372036854775807 (Long.MAX_VALUE) for unknown airtime/no next
     * episode so they will get sorted last.
     */
    public static final String UNKNOWN_NEXT_AIR_DATE = "9223372036854775807";

    /**
     * Looks up the episodes of a given season and stores the count of already
     * aired, but not watched ones in the seasons watchcount.
     * 
     * @param seasonid
     */
    public static void updateUnwatchedCount(Context context, String seasonid) {
        ContentResolver resolver = context.getContentResolver();
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);
        Uri episodesOfSeasonUri = Episodes.buildEpisodesOfSeasonUri(seasonid);

        // all a seasons episodes
        Cursor total = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, null, null, null);
        final int totalcount = total.getCount();
        total.close();

        // unwatched, aired episodes
        String selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + " like '%-%'"
                + " AND " + Episodes.FIRSTAIRED + "<=?";
        Cursor unwatched = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", today
        }, null);
        final int count = unwatched.getCount();
        unwatched.close();

        // unwatched, aired in the future episodes
        selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + ">?";
        Cursor unaired = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", today
        }, null);
        final int unaired_count = unaired.getCount();
        unaired.close();

        // unwatched, no airdate
        selection = Episodes.WATCHED + "=? AND " + Episodes.FIRSTAIRED + "=?";
        Cursor noairdate = resolver.query(episodesOfSeasonUri, new String[] {
            Episodes._ID
        }, selection, new String[] {
                "0", ""
        }, null);

        final int noairdate_count = noairdate.getCount();
        noairdate.close();

        ContentValues update = new ContentValues();
        update.put(Seasons.WATCHCOUNT, count);
        update.put(Seasons.UNAIREDCOUNT, unaired_count);
        update.put(Seasons.NOAIRDATECOUNT, noairdate_count);
        update.put(Seasons.TOTALCOUNT, totalcount);
        resolver.update(Seasons.buildSeasonUri(seasonid), update, null, null);
    }

    /**
     * Returns all episodes that air today or later. Using Pacific Time to
     * determine today. Excludes shows that are hidden.
     * 
     * @return Cursor including episodes with show title, network, airtime and
     *         posterpath.
     */
    public static Cursor getUpcomingEpisodes(Context context) {
        Calendar cal = Calendar.getInstance();
        // go an hour back in time, so episodes move to recent one hour late
        cal.add(Calendar.HOUR_OF_DAY, -1);
        final String recentThreshold = String.valueOf(cal.getTimeInMillis());
        String query = UpcomingQuery.QUERY_UPCOMING;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean isOnlyFavorites = prefs.getBoolean(SeriesGuidePreferences.KEY_ONLYFAVORITES, false);
        String[] selectionArgs;
        if (isOnlyFavorites) {
            query += UpcomingQuery.SELECTION_ONLYFAVORITES;
            selectionArgs = new String[] {
                    recentThreshold, "0", "1"
            };
        } else {
            selectionArgs = new String[] {
                    recentThreshold, "0"
            };
        }

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW,
                UpcomingQuery.PROJECTION, query, selectionArgs, UpcomingQuery.SORTING_UPCOMING);
    }

    /**
     * Return all episodes that aired the day before and earlier. Using Pacific
     * Time to determine today. Excludes shows that are hidden.
     * 
     * @param context
     * @return Cursor including episodes with show title, network, airtime and
     *         posterpath.
     */
    public static Cursor getRecentEpisodes(Context context) {
        Calendar cal = Calendar.getInstance();
        // go an hour back in time, so episodes move to recent one hour late
        cal.add(Calendar.HOUR_OF_DAY, -1);
        final String recentThreshold = String.valueOf(cal.getTimeInMillis());
        String query = UpcomingQuery.QUERY_RECENT;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean isOnlyFavorites = prefs.getBoolean(SeriesGuidePreferences.KEY_ONLYFAVORITES, false);
        String[] selectionArgs;
        if (isOnlyFavorites) {
            query += UpcomingQuery.SELECTION_ONLYFAVORITES;
            selectionArgs = new String[] {
                    recentThreshold, "0", "1"
            };
        } else {
            selectionArgs = new String[] {
                    recentThreshold, "0"
            };
        }

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW,
                UpcomingQuery.PROJECTION, query, selectionArgs, UpcomingQuery.SORTING_RECENT);
    }

    /**
     * Updates the given boolean to the EPISODE_WATCHED column of the given
     * episode row. Be aware that the database stores the value as an integer.
     * 
     * @param rowid
     * @param state
     */
    public static void markEpisode(Context context, String episodeId, boolean state) {
        if (context == null) {
            return;
        }

        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, state);

        context.getContentResolver()
                .update(Episodes.buildEpisodeUri(episodeId), values, null, null);
        context.getContentResolver().notifyChange(Episodes.CONTENT_URI, null);
    }

    /**
     * Marks the next episode (if there is one) of this show as watched.
{'fraction_non_alphanumeric': 0.09091533337911145, 'fraction_numerical': 0.006729382682139669, 'mean_word_length': 2.72100153295861, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 48, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3434649', 'n_tokens_mistral': 4174, 'n_tokens_neox': 3972, 'n_words': 985}