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

import com.battlelancer.seriesguide.Constants;
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.provider.SeriesGuideDatabase.Tables;
import com.battlelancer.seriesguide.ui.SeriesGuidePreferences;
import com.battlelancer.thetvdbapi.ImageCache;
import com.battlelancer.thetvdbapi.Series;

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.Date;
import java.util.HashSet;

public class DBUtils {

    static final String TAG = "SeriesDatabase";

    /**
     * Use 9999 for unkown airdates/no next episodes, sorting then assumes year
     * 9999 and sorts these last.
     */
    public static final String UNKNOWN_NEXT_AIR_DATE = "9999";

    /**
     * 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 upcoming episodes together with their shows title, network,
     * airtime and posterpath.
     * 
     * @return Cursor including all upcoming episodes with show title, network,
     *         airtime and posterpath.
     */
    public static Cursor getUpcomingEpisodes(Context context) {
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);

        String[] projection = new String[] {
                Tables.EPISODES + "." + Episodes._ID, Episodes.TITLE, Episodes.WATCHED,
                Episodes.NUMBER, Episodes.SEASON, Episodes.FIRSTAIRED, Shows.TITLE, Shows.AIRSTIME,
                Shows.NETWORK, Shows.POSTER
        };
        String sortOrder = Episodes.FIRSTAIRED + " ASC," + Shows.AIRSTIME + " ASC," + Shows.TITLE
                + " ASC";

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW, projection,
                Episodes.FIRSTAIRED + ">=?", new String[] {
                    today
                }, sortOrder);
    }

    public static Cursor getRecentEpisodes(Context context) {
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);

        String[] projection = new String[] {
                Tables.EPISODES + "." + Episodes._ID, Episodes.TITLE, Episodes.WATCHED,
                Episodes.NUMBER, Episodes.SEASON, Episodes.FIRSTAIRED, Shows.TITLE, Shows.AIRSTIME,
                Shows.NETWORK, Shows.POSTER
        };
        String sortOrder = Episodes.FIRSTAIRED + " DESC," + Shows.AIRSTIME + " ASC," + Shows.TITLE
                + " ASC";

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW, projection,
                Episodes.FIRSTAIRED + "<?", new String[] {
                    today
                }, sortOrder);
    }

    /**
     * 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.
     * 
     * @param seriesid
     */
    public static void markNextEpisode(Context context, long seriesid) {
        Cursor show = context.getContentResolver().query(
                Shows.buildShowUri(String.valueOf(seriesid)), new String[] {
                    Shows.NEXTEPISODE
                }, null, null, null);
        show.moveToFirst();
        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, true);
        String episodeid = show.getString(show.getColumnIndexOrThrow(Shows.NEXTEPISODE));
        if (episodeid.length() != 0) {
            context.getContentResolver().update(Episodes.buildEpisodeUri(episodeid), values, null,
                    null);
        }
        show.close();
    }

    /**
     * Updates all EPISODE_WATCHED columns of the episodes of the given season
     * with the given boolean. Be aware that the database stores the value as an
     * integer.
     * 
     * @param seasonid
     * @param state
     */
    public static void markSeasonEpisodes(Context context, String seasonId, boolean state) {
        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, state);
        context.getContentResolver().update(Episodes.buildEpisodesOfSeasonUri(seasonId), values,
                null, null);
    }

    /**
     * Marks all episodes of a show that have aired before the air date of the
     * given episode.
     * 
     * @param context
     * @param episodeId
     */
    public static void markUntilHere(Context context, String episodeId) {
        Cursor episode = context.getContentResolver().query(Episodes.buildEpisodeUri(episodeId),
                new String[] {
                        Episodes.FIRSTAIRED, Shows.REF_SHOW_ID
                }, null, null, null);
        episode.moveToFirst();
        final String untilDate = episode.getString(0);
        final String showId = episode.getString(1);
        episode.close();

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

        context.getContentResolver().update(Episodes.buildEpisodesOfShowUri(showId), values,
                Episodes.FIRSTAIRED + "<? AND " + Episodes.FIRSTAIRED + "!=''", new String[] {
                    untilDate
                });
        context.getContentResolver().notifyChange(Episodes.CONTENT_URI, null);
    }

    /**
     * Fetches the row to a given show id and returns the results an Series
     * object. Returns {@code null} if there is no show with that id.
     * 
     * @param seriesid<commit_msg>Check for empty untilDate, not that it matters, just to be safe and potentially faster.<commit_after>
package com.battlelancer.seriesguide.util;

import com.battlelancer.seriesguide.Constants;
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.provider.SeriesGuideDatabase.Tables;
import com.battlelancer.seriesguide.ui.SeriesGuidePreferences;
import com.battlelancer.thetvdbapi.ImageCache;
import com.battlelancer.thetvdbapi.Series;

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.Date;
import java.util.HashSet;

public class DBUtils {

    static final String TAG = "SeriesDatabase";

    /**
     * Use 9999 for unkown airdates/no next episodes, sorting then assumes year
     * 9999 and sorts these last.
     */
    public static final String UNKNOWN_NEXT_AIR_DATE = "9999";

    /**
     * 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 upcoming episodes together with their shows title, network,
     * airtime and posterpath.
     * 
     * @return Cursor including all upcoming episodes with show title, network,
     *         airtime and posterpath.
     */
    public static Cursor getUpcomingEpisodes(Context context) {
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);

        String[] projection = new String[] {
                Tables.EPISODES + "." + Episodes._ID, Episodes.TITLE, Episodes.WATCHED,
                Episodes.NUMBER, Episodes.SEASON, Episodes.FIRSTAIRED, Shows.TITLE, Shows.AIRSTIME,
                Shows.NETWORK, Shows.POSTER
        };
        String sortOrder = Episodes.FIRSTAIRED + " ASC," + Shows.AIRSTIME + " ASC," + Shows.TITLE
                + " ASC";

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW, projection,
                Episodes.FIRSTAIRED + ">=?", new String[] {
                    today
                }, sortOrder);
    }

    public static Cursor getRecentEpisodes(Context context) {
        Date date = new Date();
        String today = Constants.theTVDBDateFormat.format(date);

        String[] projection = new String[] {
                Tables.EPISODES + "." + Episodes._ID, Episodes.TITLE, Episodes.WATCHED,
                Episodes.NUMBER, Episodes.SEASON, Episodes.FIRSTAIRED, Shows.TITLE, Shows.AIRSTIME,
                Shows.NETWORK, Shows.POSTER
        };
        String sortOrder = Episodes.FIRSTAIRED + " DESC," + Shows.AIRSTIME + " ASC," + Shows.TITLE
                + " ASC";

        return context.getContentResolver().query(Episodes.CONTENT_URI_WITHSHOW, projection,
                Episodes.FIRSTAIRED + "<?", new String[] {
                    today
                }, sortOrder);
    }

    /**
     * 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.
     * 
     * @param seriesid
     */
    public static void markNextEpisode(Context context, long seriesid) {
        Cursor show = context.getContentResolver().query(
                Shows.buildShowUri(String.valueOf(seriesid)), new String[] {
                    Shows.NEXTEPISODE
                }, null, null, null);
        show.moveToFirst();
        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, true);
        String episodeid = show.getString(show.getColumnIndexOrThrow(Shows.NEXTEPISODE));
        if (episodeid.length() != 0) {
            context.getContentResolver().update(Episodes.buildEpisodeUri(episodeid), values, null,
                    null);
        }
        show.close();
    }

    /**
     * Updates all EPISODE_WATCHED columns of the episodes of the given season
     * with the given boolean. Be aware that the database stores the value as an
     * integer.
     * 
     * @param seasonid
     * @param state
     */
    public static void markSeasonEpisodes(Context context, String seasonId, boolean state) {
        ContentValues values = new ContentValues();
        values.put(Episodes.WATCHED, state);
        context.getContentResolver().update(Episodes.buildEpisodesOfSeasonUri(seasonId), values,
                null, null);
    }

    /**
     * Marks all episodes of a show that have aired before the air date of the
     * given episode.
     * 
     * @param context
     * @param episodeId
     */
    public static void markUntilHere(Context context, String episodeId) {
        Cursor episode = context.getContentResolver().query(Episodes.buildEpisodeUri(episodeId),
                new String[] {
                        Episodes.FIRSTAIRED, Shows.REF_SHOW_ID
                }, null, null, null);
        episode.moveToFirst();
        final String untilDate = episode.getString(0);
        final String showId = episode.getString(1);
        episode.close();

        if (untilDate.length() != 0) {
            ContentValues values = new ContentValues();
            values.put(Episodes.WATCHED, true);

            context.getContentResolver().update(Episodes.buildEpisodesOfShowUri(showId), values,
                    Episodes.FIRSTAIRED + "<? AND " + Episodes.FIRSTAIRED + "!=''", new String[] {
                        untilDate
                    });
            context.getContentResolver().notifyChange(Episodes.CONTENT_URI, null);
        }
    }

    /**
     * Fetches the row to a given show id and returns the results an Series
     * object. Returns {@code null} if there is no show with that id.
     * 
     * @param seriesid
{'fraction_non_alphanumeric': 0.09910669138715658, 'fraction_numerical': 0.002078768470138772, 'mean_word_length': 2.5101557878130545, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 114, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7319729', 'n_tokens_mistral': 5247, 'n_tokens_neox': 4943, 'n_words': 1194}