public class TMDbTvShowService extends TvShowApiService {

	private static TMDbTvShowService mService;
	
	private final String mTmdbApiKey;
	private final Context mContext;

	public static TMDbTvShowService getInstance(Context context) {
		if (mService == null)
			mService = new TMDbTvShowService(context);
		return mService;
	}
	
	private TMDbTvShowService(Context context) {
		mContext = context;
		mTmdbApiKey = MizLib.getTmdbApiKey(mContext);
	}

	@Override
	public List<TvShow> search(String query, String language) {
		language = getLanguage(language);

		String serviceUrl = "";

		try {
			serviceUrl = "https://api.themoviedb.org/3/search/tv?query=" + URLEncoder.encode(query, "utf-8") + "&language=" + language + "&api_key=" + mTmdbApiKey;
		} catch (UnsupportedEncodingException e) {}

		return getListFromUrl(serviceUrl);
	}

	@Override
	public List<TvShow> search(String query, String year, String language) {
		language = getLanguage(language);

		String serviceUrl = "";

		try {
			serviceUrl = "https://api.themoviedb.org/3/search/tv?query=" + URLEncoder.encode(query, "utf-8") + "&language=" + language + "&first_air_date_year=" + year + "&api_key=" + mTmdbApiKey;
		} catch (UnsupportedEncodingException e) {}

		return getListFromUrl(serviceUrl);
	}

	@Override
	public List<TvShow> searchByImdbId(String imdbId, String language) {
		language = getLanguage(language);

		ArrayList<TvShow> results = new ArrayList<TvShow>();

		try {
			JSONObject jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/find/" + imdbId + "?language=" + language + "&external_source=imdb_id&api_key=" + mTmdbApiKey);
			JSONArray array = jObject.getJSONArray("tv_results");

			String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);
			String imageSizeUrl = MizLib.getImageUrlSize(mContext);

			for (int i = 0; i < array.length(); i++) {
				TvShow show = new TvShow();
				show.setTitle(array.getJSONObject(i).getString("name"));
				show.setOriginalTitle(array.getJSONObject(i).getString("original_name"));
				show.setFirstAired(array.getJSONObject(i).getString("first_air_date"));
				show.setDescription(""); // TMDb doesn't support descriptions in search results
				show.setRating(String.valueOf(array.getJSONObject(i).getDouble("vote_average")));
				show.setId(String.valueOf(array.getJSONObject(i).getInt("id")));
				show.setCoverUrl(baseUrl + imageSizeUrl + array.getJSONObject(i).getString("poster_path"));
				results.add(show);
			}
		} catch (JSONException e) {}

		return results;
	}

	@Override
	public TvShow get(String id, String language) {
		language = getLanguage(language);

		TvShow show = new TvShow();
		show.setId(id);

		String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);

		JSONObject jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "?api_key=" + mTmdbApiKey + "&language=" + language + "&append_to_response=credits,images,external_ids");

		// Set title
		show.setTitle(MizLib.getStringFromJSONObject(jObject, "name", ""));

		// Set description
		show.setDescription(MizLib.getStringFromJSONObject(jObject, "overview", ""));

		if (!language.equals("en")) { // This is a localized search - let's fill in the blanks
			JSONObject englishResults = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "?api_key=" + mTmdbApiKey + "&language=en");

			if (TextUtils.isEmpty(show.getTitle()))
				show.setTitle(MizLib.getStringFromJSONObject(englishResults, "name", ""));

			if (TextUtils.isEmpty(show.getDescription()))
				show.setDescription(MizLib.getStringFromJSONObject(englishResults, "overview", ""));
		}

		// Set actors
		try {
			StringBuilder actors = new StringBuilder();

			JSONArray array = jObject.getJSONObject("credits").getJSONArray("cast");
			for (int i = 0; i < array.length(); i++) {
				actors.append(array.getJSONObject(i).getString("name"));
				actors.append("|");
			}

			show.setActors(actors.toString());
		} catch (Exception e) {}

		// Set genres
		try {
			String genres = "";
			for (int i = 0; i < jObject.getJSONArray("genres").length(); i++)
				genres = genres + jObject.getJSONArray("genres").getJSONObject(i).getString("name") + ", ";
			show.setGenres(genres.substring(0, genres.length() - 2));
		} catch (Exception e) {}

		// Set rating
		show.setRating(MizLib.getStringFromJSONObject(jObject, "vote_average", "0.0"));

		// Set cover path
		show.setCoverUrl(baseUrl + MizLib.getImageUrlSize(mContext) + MizLib.getStringFromJSONObject(jObject, "poster_path", ""));

		// Set backdrop path
		show.setBackdropUrl(baseUrl + MizLib.getBackdropUrlSize(mContext) + MizLib.getStringFromJSONObject(jObject, "backdrop_path", ""));

		// Set certification - not available with TMDb
		show.setCertification("");

		try {
			// Set runtime
			show.setRuntime(String.valueOf(jObject.getJSONArray("episode_run_time").getInt(0)));
		} catch (JSONException e) {}

		// Set first aired date
		show.setFirstAired(MizLib.getStringFromJSONObject(jObject, "first_air_date", ""));

		try {
			// Set IMDb ID
			show.setIMDbId(jObject.getJSONObject("external_ids").getString("imdb_id"));
		} catch (JSONException e) {}

		// Seasons
		try {
			JSONArray seasons = jObject.getJSONArray("seasons");

			for (int i = 0; i < seasons.length(); i++) {
				Season s = new Season();

				s.setSeason(seasons.getJSONObject(i).getInt("season_number"));
				s.setCoverPath(baseUrl + MizLib.getImageUrlSize(mContext) + MizLib.getStringFromJSONObject(seasons.getJSONObject(i), "poster_path", ""));

				show.addSeason(s);
			}
		} catch (JSONException e) {}

		// Episode details
		for (Season s : show.getSeasons()) {
			jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "/season/" + s.getSeason() + "?api_key=" + mTmdbApiKey);
			try {
				JSONArray episodes = jObject.getJSONArray("episodes");
				for (int i = 0; i < episodes.length(); i++) {
					Episode ep = new Episode();
					ep.setSeason(s.getSeason());
					ep.setEpisode(episodes.getJSONObject(i).getInt("episode_number"));
					ep.setTitle(episodes.getJSONObject(i).getString("name"));
					ep.setAirdate(episodes.getJSONObject(i).getString("air_date"));
					ep.setDescription(episodes.getJSONObject(i).getString("overview"));
					ep.setRating(MizLib.getStringFromJSONObject(episodes.getJSONObject(i), "vote_average", "0.0"));

					try {
						// This is quite nasty... An HTTP call for each episode, yuck!
						// Sadly, this is needed in order to get proper screenshot URLS
						// and info about director, writer and guest stars
						JSONObject episodeCall = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "/season/" + s.getSeason() + "/episode/" + ep.getEpisode() + "?api_key=" + mTmdbApiKey + "&append_to_response=credits,images");

						// Screenshot URL in the correct size
						JSONArray images = episodeCall.getJSONObject("images").getJSONArray("stills");
						if (images.length() > 0) {
							JSONObject firstImage = images.getJSONObject(0);
							int width = firstImage.getInt("width");
							if (width < 500) {
								ep.setScreenshotUrl(baseUrl + "original" + MizLib.getStringFromJSONObject(firstImage, "file_path", ""));
							} else {
								ep.setScreenshotUrl(baseUrl + MizLib.getBackdropThumbUrlSize(mContext) + MizLib.getStringFromJSONObject(firstImage, "file_path", ""));
							}
						}

						try {
							// Guest stars
							StringBuilder actors = new StringBuilder();
							JSONArray guest_stars = episodeCall.getJSONObject("credits").getJSONArray("guest_stars");

							for (int j = 0; j < guest_stars.length(); j++) {
								actors.append(guest_stars.getJSONObject(j).getString("name"));
								actors.append("|");
							}

							ep.setGueststars(actors.toString());
						} catch (Exception e) {}

						try {
							// Crew information
							StringBuilder director = new StringBuilder(), writer = new StringBuilder();
							JSONArray crew = episodeCall.getJSONObject("credits").getJSONArray("crew");

							for (int j = 0; j < crew.length(); j++) {
								if (crew.getJSONObject(j).getString("job").equals("Director")) {
									director.append(crew.getJSONObject(j).getString("name"));
									director.append("|");
								} else if (crew.getJSONObject(j).getString("job").equals("Writer")) {
									writer.append(crew.getJSONObject(j).getString("name"));
									writer.append("|");
								}
							}

							ep.setDirector(director.toString());
							ep.setWriter(writer.toString());
							
						} catch (Exception e) {}

					} catch (Exception e) {}

					show.addEpisode(ep);
				}
			} catch (JSONException e) {}
		}

		return show;
	}

	private ArrayList<TvShow> getListFromUrl(String serviceUrl) {
		ArrayList<TvShow> results = new ArrayList<TvShow>();

		try {
			JSONObject jObject = MizLib.getJSONObject(mContext, serviceUrl);
			JSONArray array = jObject.getJSONArray("results");

			String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);
			String imageSizeUrl = MizLib.getImageUrlSize(mContext);

			for (int i = 0; i < array.length(); i++) {
				TvShow show = new TvShow();
				show.setTitle(array.getJSONObject(i).getString("name"));
				show.setOriginalTitle(array.getJSONObject(i).getString("original_name"));
				show.setFirstAired(array.getJSONObject(i).getString("first_air_date"));
				show.setDescription(""); // TMDb doesn't support descriptions in search results
				show.setRating(String.valueOf(array.getJSONObject(i).getDouble("vote_average")));
				show.setId(String.valueOf(array.getJSONObject(i).getInt("id")));
				show.setCoverUrl(baseUrl + imageSizeUrl + array.getJSONObject(i).getString("poster_path"));
				results.add(show);
			}
		} catch (JSONException e) {}

		return results;
	}

	@Override
	public List<String> getCovers(String id) {
		ArrayList<String> covers = new ArrayList<String>();
		String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);
		
		try {
			JSONObject jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "/images" + "?api_key=" + mTmdbApiKey);
			JSONArray jArray = jObject.getJSONArray("posters");
			for (int i = 0; i < jArray.length(); i++) {
				covers.add(baseUrl + MizLib.getImageUrlSize(mContext) + MizLib.getStringFromJSONObject(jArray.getJSONObject(i), "file_path", ""));
			}
		} catch (JSONException e) {}

		return covers;
	}

	@Override
	public List<String> getBackdrops(String id) {
		ArrayList<String> covers = new ArrayList<String>();
		String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);
		
		try {
			JSONObject jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "/images" + "?api_key=" + mTmdbApiKey);
			JSONArray jArray = jObject.getJSONArray("backdrops");
			for (int i = 0; i < jArray.length(); i++) {
				covers.add(baseUrl + MizLib.getBackdropThumbUrlSize(mContext) + MizLib.getStringFromJSONObject(jArray.getJSONObject(i), "file_path", ""));
			}
		} catch (JSONException e) {}

		return covers;
	}
	
	/**
	 * Get the language code or a default one if
	 * the supplied one is empty or {@link null}.
	 * @param language
	 * @return Language code
	 */
	@Override
	public String getLanguage(String language) {
		if (TextUtils.isEmpty(language))
			language = "en";
		return language;
	}

	@Override
	public List<TvShow> searchNgram(String query, String language) {
		language = getLanguage(language);

		String serviceUrl = "";

		try {
			serviceUrl = "https://api.themoviedb.org/3/search/tv?query=" + URLEncoder.encode(query, "utf-8") + "&language=" + language + "&search_type=ngram&api_key=" + mTmdbApiKey;
		} catch (UnsupportedEncodingException e) {}

		return getListFromUrl(serviceUrl);
	}

	@Override
	public List<Actor> getActors(String id) {
		ArrayList<Actor> results = new ArrayList<Actor>();

		String baseUrl = MizLib.getTmdbImageBaseUrl(mContext);

		try {
			JSONObject jObject = MizLib.getJSONObject(mContext, "https://api.themoviedb.org/3/tv/" + id + "/credits?api_key=" + mTmdbApiKey);
			JSONArray jArray = jObject.getJSONArray("cast");

			Set<String> actorIds = new HashSet<String>();

			for (int i = 0; i < jArray.length(); i++) {
				if (!actorIds.contains(jArray.getJSONObject(i).getString("id"))) {
					actorIds.add(jArray.getJSONObject(i).getString("id"));

					results.add(new Actor(
							jArray.getJSONObject(i).getString("name"),
							jArray.getJSONObject(i).getString("character"),
							jArray.getJSONObject(i).getString("id"),
							baseUrl + MizLib.getActorUrlSize(mContext) + jArray.getJSONObject(i).getString("profile_path")));
				}
			}
		} catch (Exception ignored) {}

		return results;
	}
} public class DbAdapterTvShowEpisodeMappings extends AbstractDbAdapter {

	public static final String KEY_FILEPATH = "filepath";
	public static final String KEY_SHOW_ID = "show_id";
	public static final String KEY_SEASON = "season";
	public static final String KEY_EPISODE = "episode";
	public static final String KEY_IGNORED = "ignored";

	public static final String DATABASE_TABLE = "episodes_map";

	public static final String[] ALL_COLUMNS = new String[]{KEY_FILEPATH, KEY_SHOW_ID, KEY_SEASON, KEY_EPISODE, KEY_IGNORED};

	public DbAdapterTvShowEpisodeMappings(Context context) {
		super(context);
	}

	public long createFilepathMapping(String filepath, String showId, String season, String episode) {
		if (!filepathExists(showId, season, episode, filepath)) {
			// Add values to a ContentValues object
			ContentValues values = new ContentValues();
			values.put(KEY_FILEPATH, filepath);
			values.put(KEY_SHOW_ID, showId);
			values.put(KEY_SEASON, season);
			values.put(KEY_EPISODE, episode);
			values.put(KEY_IGNORED, 0);

			// Insert into database
			return mDatabase.insert(DATABASE_TABLE, null, values);
		}
		return -1;
	}

	public boolean filepathExists(String showId, String season, String episode, String filepath) {
		String[] selectionArgs = new String[]{showId, filepath, season, episode};
		Cursor cursor = mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ? AND " + KEY_FILEPATH + " = ? AND "
				+ KEY_SEASON + " = ? AND " + KEY_EPISODE + " = ?", selectionArgs, null, null, null);
		boolean result = false;

		if (cursor != null) {
			try {
				if (cursor.getCount() > 0)
					result = true;
			} catch (Exception e) {
			} finally {
				cursor.close();
			}
		}

		return result;
	}

	public String getFirstFilepath(String showId, String season, String episode) {
		Cursor cursor = mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ? AND " + KEY_SEASON + " = ? AND " + KEY_EPISODE + " = ?",
				new String[]{showId, season, episode}, null, null, null);
		String filepath = "";

		if (cursor != null) {
			try {
				if (cursor.moveToFirst()) {
					filepath = cursor.getString(cursor.getColumnIndex(KEY_FILEPATH));
				}
			} catch (Exception e) {
			} finally {
				cursor.close();
			}
		}

		return filepath;
	}

	public ArrayList<String> getFilepathsForEpisode(String showId, String season, String episode) {
		ArrayList<String> paths = new ArrayList<String>();

		Cursor cursor = mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ? AND " + KEY_SEASON + " = ? AND " + KEY_EPISODE + " = ?",
				new String[]{showId, season, episode}, null, null, null);

		if (cursor != null) {
			try {
				while (cursor.moveToNext()) {
					paths.add(cursor.getString(cursor.getColumnIndex(KEY_FILEPATH)));
				}
			} catch (Exception e) {
			} finally {
				cursor.close();
			}
		}

		return paths;
	}

    public ArrayList<String> getFilepathsForShow(String showId) {
        ArrayList<String> paths = new ArrayList<String>();

        Cursor cursor = mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ?" ,
                new String[]{showId}, null, null, null);

        if (cursor != null) {
            try {
                while (cursor.moveToNext()) {
                    paths.add(cursor.getString(cursor.getColumnIndex(KEY_FILEPATH)));
                }
            } catch (Exception e) {
            } finally {
                cursor.close();
            }
        }

        return paths;
    }

	public Cursor getAllUnidentifiedFilepaths() {
		String[] selectionArgs = new String[]{DbAdapterTvShows.UNIDENTIFIED_ID};
		return mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ?", selectionArgs, null, null, null);
	}
	
	public Cursor getAllFilepaths() {
		return mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, null, null, null, null, null);
	}

	public Cursor getAllFilepathInfo(String filepath) {
		String[] selectionArgs = new String[]{filepath};
		return mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_FILEPATH + " = ?", selectionArgs, null, null, null);
	}

	public Cursor getAllFilepaths(String showId) {
		return mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ?", new String[]{showId}, null, null, null);
	}

	public boolean deleteFilepath(String filepath) {
		String[] selectionArgs = new String[]{filepath};
		return mDatabase.delete(DATABASE_TABLE, KEY_FILEPATH + " = ?", selectionArgs) > 0;
	}

	public boolean deleteAllFilepaths(String showId) {
		return mDatabase.delete(DATABASE_TABLE, KEY_SHOW_ID + " = ?", new String[]{showId}) > 0;
	}

	public boolean deleteAllFilepaths() {
		return mDatabase.delete(DATABASE_TABLE, null, null) > 0;
	}
	
	public boolean deleteAllUnidentifiedFilepaths() {
		return mDatabase.delete(DATABASE_TABLE, KEY_SHOW_ID + " = ?", new String[]{DbAdapterTvShows.UNIDENTIFIED_ID}) > 0;
	}

	public boolean hasMultipleFilepaths(String showId, String season, String episode) {
		Cursor cursor = mDatabase.query(DATABASE_TABLE, ALL_COLUMNS, KEY_SHOW_ID + " = ? AND " + KEY_SEASON + " = ? AND " + KEY_EPISODE + " = ?",
				new String[]{showId, season, episode}, null, null, null);
		boolean result = false;

		if (cursor != null) {
			try {
				if (cursor.getCount() > 1)
					result = true;
			} catch (Exception e) {
			} finally {
				cursor.close();
			}
		}

		return result;
	}

	public boolean removeSeason(String showId, int season) {
		return mDatabase.delete(DATABASE_TABLE, KEY_SHOW_ID + " = ? AND " + KEY_SEASON + " = ?",
				new String[]{showId, MizLib.addIndexZero(season)}) > 0;
	}

    /**
     * Used for unit testing
     * @return
     */
    public int count() {
        Cursor c = mDatabase.query(DATABASE_TABLE, new String[]{KEY_FILEPATH}, null, null, null, null, null);
        int count = c.getCount();
        c.close();
        return count;
    }
} public class DbAdapterTvShows extends AbstractDbAdapter {

	public static final String KEY_SHOW_ID = "show_id";
	public static final String KEY_SHOW_TITLE = "show_title";
	public static final String KEY_SHOW_PLOT = "show_description";
	public static final String KEY_SHOW_ACTORS = "show_actors";
	public static final String KEY_SHOW_GENRES = "show_genres";
	public static final String KEY_SHOW_RATING = "show_rating";
	public static final String KEY_SHOW_CERTIFICATION = "show_certification";
	public static final String KEY_SHOW_RUNTIME = "show_runtime";
	public static final String KEY_SHOW_FIRST_AIRDATE = "show_first_airdate";
	public static final String KEY_SHOW_FAVOURITE = "favourite";

	public static final String DATABASE_TABLE = "tvshows";
	public static final String UNIDENTIFIED_ID = "invalid";

	public static final String[] SELECT_ALL = new String[]{KEY_SHOW_ID, KEY_SHOW_TITLE, KEY_SHOW_PLOT, KEY_SHOW_ACTORS,
		KEY_SHOW_GENRES, KEY_SHOW_RATING, KEY_SHOW_RATING, KEY_SHOW_CERTIFICATION, KEY_SHOW_RUNTIME,
		KEY_SHOW_FIRST_AIRDATE, KEY_SHOW_FAVOURITE};

	public DbAdapterTvShows(Context context) {
		super(context);
	}

	public void createShow(String showId, String showTitle, String showPlot, String showActors, String showGenres, String showRating, String showCertification,
			String showRuntime, String showFirstAirdate, String isFavorite) {

        if (showId.equals(UNIDENTIFIED_ID))
            return; // We're not interesting in adding this to the TV show database

        if (getShow(showId).getCount() == 0) {
			ContentValues initialValues = createContentValues(showId, showTitle, showPlot, showActors, showGenres, showRating, showCertification, showRuntime, showFirstAirdate, isFavorite);
			mDatabase.insert(DATABASE_TABLE, null, initialValues);
		}
	}

	public boolean showExists(String id, String showTitle) {
		// Test against ID's
		String[] selectionArgs = new String[]{id};
		Cursor cursor1 = mDatabase.query(true, DATABASE_TABLE, SELECT_ALL, KEY_SHOW_ID + " = ?", selectionArgs, KEY_SHOW_ID, null, null, null);
		if (cursor1 != null) {
			try {
				if (cursor1.getCount() > 0) {
					return true;
				}
			} catch (Exception e) {} finally {
				if (!cursor1.isClosed())
					cursor1.close();
			}
		}

		// Test against the TV show title as a fall back
		selectionArgs = new String[]{showTitle};
		Cursor cursor2 = mDatabase.query(true, DATABASE_TABLE, SELECT_ALL, KEY_SHOW_TITLE + " = ?", selectionArgs, KEY_SHOW_TITLE, null, null, null);
		if (cursor2 != null) {
			try {
				if (cursor2.getCount() > 0) {
					return true;
				}
			} catch (Exception e) {} finally {
				if (!cursor2.isClosed())
					cursor2.close();
			}
		}

		return false;
	}

    public String getSingleItem(String showId, String column) {
        String singleItem = "";
        Cursor c = mDatabase.query(DATABASE_TABLE, new String[]{column}, KEY_SHOW_ID + " = ?", new String[]{showId}, null, null, null);
        if (c != null) {
            try {
                if (c.moveToFirst())
                    singleItem = c.getString(c.getColumnIndex(column));
            } catch (SQLException e) {} finally {
                c.close();
            }
        }
        return singleItem;
    }

	public String getShowId(String showTitle) {
		String[] selectionArgs = new String[]{showTitle};
		Cursor mCursor = mDatabase.query(true, DATABASE_TABLE, SELECT_ALL, KEY_SHOW_TITLE + " = ?", selectionArgs, KEY_SHOW_TITLE, null, null, null);
		if (mCursor == null)
			return "";
		try {
			if (mCursor.getCount() == 0) {
				mCursor.close();
				return "";
			}
		} catch (Exception e) {
			mCursor.close();
			return "";
		}

		mCursor.moveToFirst();
		String showId = mCursor.getString(mCursor.getColumnIndex(KEY_SHOW_ID));

		mCursor.close();
		return showId;
	}

	public boolean updateShowSingleItem(String showId, String table, String value) {
		ContentValues values = new ContentValues();
		values.put(table, value);
		return mDatabase.update(DATABASE_TABLE, values, KEY_SHOW_ID + " = ?", new String[]{showId}) > 0;
	}

	public Cursor getShow(String showId) {
		return mDatabase.query(DATABASE_TABLE, SELECT_ALL, KEY_SHOW_ID + " = ?", new String[]{showId}, null, null, null);
	}

	public String getShowTitle(String showId) {
		Cursor cursor = mDatabase.query(DATABASE_TABLE, new String[]{KEY_SHOW_TITLE, KEY_SHOW_ID}, KEY_SHOW_ID + " = ?", new String[]{showId}, null, null, null);
		if (cursor.moveToFirst()) {
			String showTitle = cursor.getString(cursor.getColumnIndex(KEY_SHOW_TITLE));
			cursor.close();
			return showTitle;
		}
		return "";
	}

	public Cursor getAllShows() {
		return mDatabase.query(DATABASE_TABLE, SELECT_ALL, "NOT(" + KEY_SHOW_ID + " = '" + UNIDENTIFIED_ID + "')", null, null, null, KEY_SHOW_TITLE + " ASC");
	}

    public Cursor getAllFavorites() {
        return mDatabase.query(DATABASE_TABLE, SELECT_ALL, KEY_SHOW_FAVOURITE + " = '1' AND NOT(" + KEY_SHOW_ID + " = '" + UNIDENTIFIED_ID + "')", null, null, null, KEY_SHOW_TITLE + " ASC");
    }

	public boolean deleteShow(String showId) {
		return mDatabase.delete(DATABASE_TABLE, KEY_SHOW_ID + "= '" + showId + "'", null) > 0;
	}

	public boolean deleteAllShowsInDatabase() {
		return mDatabase.delete(DATABASE_TABLE, null, null) > 0;
	}

	public int count() {
		Cursor c = mDatabase.query(DATABASE_TABLE, new String[] {KEY_SHOW_ID, KEY_SHOW_TITLE},
				"NOT (" + KEY_SHOW_ID + " = '" + UNIDENTIFIED_ID + "') AND NOT (" + KEY_SHOW_TITLE + " LIKE '%MizUnidentified%'" + ") AND NOT (" + KEY_SHOW_ID + " = ''" + ")", null, KEY_SHOW_ID, null, null);
		int count = c.getCount();
		c.close();
		return count;
	}

	private ContentValues createContentValues(String showId, String showTitle, String showPlot, String showActors, String showGenres, String showRating, String showCertification,
			String showRuntime, String showFirstAirdate, String isFavorite) {
		ContentValues values = new ContentValues();
		values.put(KEY_SHOW_ID, showId);
		values.put(KEY_SHOW_TITLE, showTitle);
		values.put(KEY_SHOW_PLOT, showPlot);
		values.put(KEY_SHOW_ACTORS, showActors);
		values.put(KEY_SHOW_GENRES, showGenres);
		values.put(KEY_SHOW_RATING, showRating);
		values.put(KEY_SHOW_CERTIFICATION, showCertification);
		values.put(KEY_SHOW_RUNTIME, showRuntime);
		values.put(KEY_SHOW_FIRST_AIRDATE, showFirstAirdate);
		values.put(KEY_SHOW_FAVOURITE, isFavorite);
		return values;
	}

	public ArrayList<String> getCertifications() {
		ArrayList<String> certifications = new ArrayList<String>();
		Cursor cursor = mDatabase.query(DATABASE_TABLE, new String[]{KEY_SHOW_CERTIFICATION}, null, null, KEY_SHOW_CERTIFICATION, null, null);

		if (cursor != null) {
			try {
				while (cursor.moveToNext()) {
					String certification = cursor.getString(cursor.getColumnIndex(KEY_SHOW_CERTIFICATION));
					if (!TextUtils.isEmpty(certification))
						certifications.add(certification);
				}
			} catch (Exception e) {
			} finally {
				cursor.close();
			}
		}

		return certifications;
	}

	public boolean editShow(String showId, String title, String description,
			String genres, String runtime, String rating, String firstAirDate, String certification) {
		ContentValues cv = new ContentValues();
		cv.put(KEY_SHOW_TITLE, title);
		cv.put(KEY_SHOW_PLOT, description);
		cv.put(KEY_SHOW_RATING, rating);
		cv.put(KEY_SHOW_FIRST_AIRDATE, firstAirDate);
		cv.put(KEY_SHOW_CERTIFICATION, certification);
		cv.put(KEY_SHOW_RUNTIME, runtime);
		cv.put(KEY_SHOW_GENRES, genres);
		return mDatabase.update(DATABASE_TABLE, cv, KEY_SHOW_ID + " = ?", new String[]{showId}) > 0;
	}
} public class GridSeason implements Comparable<GridSeason> {
	
	private Context mContext;
	private String mShowId, mHeaderText, mSubtitleText, mSubtitleTextSimple;
	private int mSeason, mEpisodeCount, mWatchedCount;
	private boolean mUseGridView;
	private File mCover;
	
	public GridSeason(Context context, String showId, int season, int episodeCount, int watchedCount, File cover) {
		mContext = context;
        mShowId = showId;
		mSeason = season;
		mEpisodeCount = episodeCount;
		mWatchedCount = watchedCount;
		mCover = cover;
		
		mUseGridView = PreferenceManager.getDefaultSharedPreferences(mContext).getString(TVSHOWS_COLLECTION_LAYOUT, mContext.getString(R.string.gridView)).equals(mContext.getString(R.string.gridView));

        // Header text
        mHeaderText = (mSeason == 0) ? context.getString(R.string.stringSpecials) : context.getString(R.string.showSeason) + " " + getSeasonZeroIndex();

		// Subtitle text
		StringBuilder sb = new StringBuilder();
		sb.append(getEpisodeCount()).append(" ").append(mContext.getResources().getQuantityString(R.plurals.episodes, getEpisodeCount(), getEpisodeCount()));
		
		// Change the text depending on the available space
		if (getUnwatchedCount() > 0) {
			if (MizLib.isXlargeTablet(mContext) || !mUseGridView) { // Large tablets or list view: (23 unwatched)
                sb.append(" (");
                sb.append(String.format(mContext.getString(R.string.unwatchedEpisodesCount), getUnwatchedCount()));
                sb.append(")");
            } else if (MizLib.isTablet(mContext)) { // Small tablets (23 left)
                sb.append(" (");
                sb.append(String.format(mContext.getString(R.string.leftEpisodesCount), getUnwatchedCount()));
                sb.append(")");
            } else { // Phones (23)
                sb.append(" (");
                sb.append(getUnwatchedCount());
                sb.append(")");
            }
		}

		mSubtitleText = sb.toString();

        mSubtitleTextSimple = getEpisodeCount() + " " + mContext.getResources().getQuantityString(R.plurals.episodes, getEpisodeCount(), getEpisodeCount());
	}

	public int getSeason() {
		return mSeason;
	}
	
	public String getSeasonZeroIndex() {
		return MizLib.addIndexZero(mSeason);
	}
	
	public int getEpisodeCount() {
		return mEpisodeCount;
	}
	
	public int getWatchedCount() {
		return mWatchedCount;
	}
	
	public int getUnwatchedCount() {
		return mEpisodeCount - mWatchedCount;
	}

    public String getHeaderText() {
        return mHeaderText;
    }

	public String getSubtitleText() {
		return mSubtitleText;
	}

    public String getSimpleSubtitleText() {
        return mSubtitleTextSimple;
    }

	public File getCover() {
		return mCover;
	}

    public String getShowId() {
        return mShowId;
    }

	/**
	 * Custom comparator in order to put regular seasons before specials.
	 * The comparator takes the user's sorting preference into consideration
	 * - for regular seasons only. Specials remain at the end.
	 */
	@Override
	public int compareTo(GridSeason another) {
		if (getSeason() == 0)
			return 1;
		if (another.getSeason() == 0)
			return -1;
		
		// Regular sorting
		if (getSeason() < another.getSeason())
			return -1;
		if (getSeason() > another.getSeason())
			return 1;
		return 0;
	}
} @SuppressLint("NewApi")
public class MizLib {

    public static final String TYPE = "type";
    public static final String MOVIE = "movie";
    public static final String TV_SHOW = "tvshow";
    public static final String FILESOURCE = "filesource";
    public static final String USER = "user";
    public static final String PASSWORD = "password";
    public static final String DOMAIN = "domain";
    public static final String SERVER = "server";
    public static final String SERIAL_NUMBER = "serial_number";

    public static final String allFileTypes = ".3gp.aaf.mp4.ts.webm.m4v.mkv.divx.xvid.rec.avi.flv.f4v.moi.mpeg.mpg.mts.m2ts.ogv.rm.rmvb.mov.wmv.iso.vob.ifo.wtv.pyv.ogm.img";
    public static final String IMAGE_CACHE_DIR = "thumbs";
    public static final String CHARACTER_REGEX = "[^\\w\\s]";
    public static final String[] prefixes = new String[]{"the ", "a ", "an "};

    public static final int SECOND = 1000;
    public static final int MINUTE = 60 * SECOND;
    public static final int HOUR = 60 * MINUTE;
    public static final int DAY = 24 * HOUR;
    public static final int WEEK = 7 * DAY;

    private MizLib() {} // No instantiation

    public static String getTmdbApiKey(Context context) {
        String key = context.getString(R.string.tmdb_api_key);
        if (TextUtils.isEmpty(key) || key.equals("add_your_own"))
            throw new RuntimeException("You need to add a TMDb API key!");
        return key;
    }

    public static String getTvdbApiKey(Context context) {
        String key = context.getString(R.string.tvdb_api_key);
        if (TextUtils.isEmpty(key) || key.equals("add_your_own"))
            throw new RuntimeException("You need to add a TVDb API key!");
        return key;
    }

    public static boolean isVideoFile(String s) {
        String[] fileTypes = new String[]{".3gp",".aaf.","mp4",".ts",".webm",".m4v",".mkv",".divx",".xvid",".rec",".avi",".flv",".f4v",".moi",".mpeg",".mpg",".mts",".m2ts",".ogv",".rm",".rmvb",".mov",".wmv",".iso",".vob",".ifo",".wtv",".pyv",".ogm",".img"};
        int count = fileTypes.length;
        for (int i = 0; i < count; i++)
            if (s.endsWith(fileTypes[i]))
                return true;
        return false;
    }

    /**
     * Converts the first character of a String to upper case.
     * @param s (input String)
     * @return Input String with first character in upper case.
     */
    public static String toCapitalFirstChar(String s) {
        if (TextUtils.isEmpty(s))
            return "";

        return s.substring(0, 1).toUpperCase(Locale.ENGLISH) + s.substring(1, s.length());
    }

    /**
     * Converts the first character of all words (separated by space)
     * in the String to upper case.
     * @param s (input String)
     * @return Input string with first character of all words in upper case.
     */
    public static String toCapitalWords(String s) {
        if (TextUtils.isEmpty(s))
            return "";

        StringBuilder result = new StringBuilder();
        String[] split = s.split("\\s");
        int count = split.length;
        for (int i = 0; i < count; i++)
            result.append(toCapitalFirstChar(split[i])).append(" ");
        return result.toString().trim();
    }

    /**
     * Adds spaces between capital characters.
     * @param s (input String)
     * @return Input string with spaces between capital characters.
     */
    public static String addSpaceByCapital(String s) {
        if (TextUtils.isEmpty(s))
            return "";

        StringBuilder result = new StringBuilder();
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++)
            if (chars.length > (i+1))
                if (Character.isUpperCase(chars[i]) && (Character.isLowerCase(chars[i+1]) && !Character.isSpaceChar(chars[i+1])))
                    result.append(" ").append(chars[i]);
                else
                    result.append(chars[i]);
            else
                result.append(chars[i]);
        return result.toString().trim();
    }

    /**
     * Returns any digits (numbers) in a String
     * @param s (Input string)
     * @return A string with any digits from the input string
     */
    public static String getNumbersInString(String s) {
        if (TextUtils.isEmpty(s))
            return "";

        StringBuilder result = new StringBuilder();
        char[] charArray = s.toCharArray();
        int count = charArray.length;
        for (int i = 0; i < count; i++)
            if (Character.isDigit(charArray[i]))
                result.append(charArray[i]);

        return result.toString();
    }

    public static int getCharacterCountInString(String source, char c) {
        int result = 0;
        if (!TextUtils.isEmpty(source))
            for (int i = 0; i < source.length(); i++)
                if (source.charAt(i) == c)
                    result++;
        return result;
    }

    /**
     * Determines if the application is running on a tablet
     * @param c - Context of the application
     * @return True if running on a tablet, false if on a smartphone
     */
    public static boolean isTablet(Context c) {
        return (c.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    /**
     * Determines if the application is running on a xlarge tablet
     * @param c - Context of the application
     * @return True if running on a xlarge tablet, false if on a smaller device
     */
    public static boolean isXlargeTablet(Context c) {
        return (c.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }

    /**
     * Determines if the device is in portrait mode
     * @param c - Context of the application
     * @return True if portrait mode, false if landscape mode
     */
    public static boolean isPortrait(Context c) {
        return c != null && (c.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
    }

    /**
     * Determines if the device is currently connected to a network
     * @param c - Context of the application
     * @return True if connected to a network, else false
     */
    public static boolean isOnline(Context c) {
        ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] netInfo = cm.getAllNetworkInfo();
        int count = netInfo.length;
        for (int i = 0; i < count; i++)
            if (netInfo[i] != null && netInfo[i].isConnected()) return true;
        return false;
    }

    /**
     * Determines if the device is currently connected to a WiFi or Ethernet network
     * @param c - Context of the application
     * @return True if connected to a network, else false
     */
    public static boolean isWifiConnected(Context c) {
        if (c!= null) {
            ConnectivityManager connManager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo[] connections = connManager.getAllNetworkInfo();
            int count = connections.length;
            for (int i = 0; i < count; i++)
                if (connections[i]!= null && connections[i].getType() == ConnectivityManager.TYPE_WIFI && connections[i].isConnectedOrConnecting() ||
                        connections[i]!= null &&  connections[i].getType() == ConnectivityManager.TYPE_ETHERNET && connections[i].isConnectedOrConnecting())
                    return true;
        }
        return false;
    }

    /**
     * Returns a custom theme background image as Bitmap.
     * @param height
     * @param width
     * @return Bitmap with the background image
     */
    public static Bitmap getCustomThemeBackground(int height, int width, String url) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Config.RGB_565;
            options.inDither = true;

            Bitmap bm = BitmapFactory.decodeFile(url, options);

            float scaleWidth = bm.getWidth() / ((float) width);
            float scaleHeight = bm.getHeight() / ((float) height);

            if (scaleWidth > scaleHeight) bm = Bitmap.createScaledBitmap(bm, (int)(bm.getWidth() / scaleHeight), (int)(bm.getHeight() / scaleHeight), true);
            else bm = Bitmap.createScaledBitmap(bm, (int)(bm.getWidth() / scaleWidth), (int)(bm.getHeight() / scaleWidth), true);

            bm = Bitmap.createBitmap(bm, (bm.getWidth() - width) / 2, (bm.getHeight() - height) / 2, width, height);
            return bm;
        } catch (Exception e) {
            return null;
        }
    }

    public static final long GB = 1000 * 1000 * 1000;
    public static final long MB = 1000 * 1000;
    public static final long KB = 1000;

    /**
     * Returns the input file size as a string in either KB, MB or GB
     * @param size (as bytes)
     * @return Size as readable string
     */
    public static String filesizeToString(long size) {
        if ((size / GB) >= 1) return substring(String.valueOf(((double) size / (double) GB)), 4) + " GB"; // GB
        else if ((size / MB) >= 1) return (size / MB) + " MB"; // MB
        else return (size / KB) + " KB"; // KB
    }

    /**
     * Returns a string with a length trimmed to the specified max length
     * @param s
     * @param maxLength
     * @return String with a length of the specified max length
     */
    public static String substring(String s, int maxLength) {
        if (s.length() >= maxLength) return s.substring(0, maxLength);
        else return s;
    }

    /**
     * Add a padding with a height of the ActionBar to the top of a given View
     * @param c
     * @param v
     */
    public static void addActionBarPadding(Context c, View v) {
        int mActionBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        v.setPadding(0, mActionBarHeight, 0, 0);
    }

    /**
     * Add a padding with a combined height of the ActionBar and Status bar to the top of a given View
     * @param c
     * @param v
     */
    public static void addActionBarAndStatusBarPadding(Context c, View v) {
        int mActionBarHeight = 0, mStatusBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        if (hasKitKat())
            mStatusBarHeight = convertDpToPixels(c, 25);

        v.setPadding(0, mActionBarHeight + mStatusBarHeight, 0, 0);
    }

    /**
     * Add a padding with a height of the ActionBar to the bottom of a given View
     * @param c
     * @param v
     */
    public static void addActionBarPaddingBottom(Context c, View v) {
        int mActionBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        v.setPadding(0, 0, 0, mActionBarHeight);
    }

    /**
     * Add a margin with a height of the ActionBar to the top of a given View contained in a FrameLayout
     * @param c
     * @param v
     */
    public static void addActionBarMargin(Context c, View v) {
        int mActionBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.setMargins(0, mActionBarHeight, 0, 0);
        v.setLayoutParams(params);
    }

    /**
     * Add a margin with a combined height of the ActionBar and Status bar to the top of a given View contained in a FrameLayout
     * @param c
     * @param v
     */
    public static void addActionBarAndStatusBarMargin(Context c, View v) {
        int mActionBarHeight = 0, mStatusBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        if (hasKitKat())
            mStatusBarHeight = convertDpToPixels(c, 25);

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.setMargins(0, mActionBarHeight + mStatusBarHeight, 0, 0);

        v.setLayoutParams(params);
    }

    /**
     * Add a margin with a combined height of the ActionBar and Status bar to the top of a given View contained in a FrameLayout
     * @param c
     * @param v
     */
    public static void addActionBarAndStatusBarMargin(Context c, View v, FrameLayout.LayoutParams layoutParams) {
        int mActionBarHeight = 0, mStatusBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        if (hasKitKat())
            mStatusBarHeight = convertDpToPixels(c, 25);

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.setMargins(0, mActionBarHeight + mStatusBarHeight, 0, 0);
        params.gravity = layoutParams.gravity;
        v.setLayoutParams(params);
    }

    /**
     * Add a margin with a height of the ActionBar to the top of a given View contained in a FrameLayout
     * @param c
     * @param v
     */
    public static void addActionBarMarginBottom(Context c, View v) {
        int mActionBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            mActionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.setMargins(0, 0, 0, mActionBarHeight);
        v.setLayoutParams(params);
    }

    public static void addNavigationBarPadding(Context c, View v) {
        v.setPadding(0, 0, 0, getNavigationBarHeight(c));
    }

    public static void addNavigationBarMargin(Context c, View v) {
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.setMargins(0, 0, 0, getNavigationBarHeight(c));
        v.setLayoutParams(params);
    }

    public static boolean hasICSMR1() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
    }

    public static boolean hasJellyBean() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    public static boolean hasJellyBeanMR1() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
    }

    public static boolean hasJellyBeanMR2() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
    }

    public static boolean hasKitKat() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    }

    public static boolean isKitKat() {
        return Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
    }

    public static boolean hasLollipop() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    public static int getThumbnailNotificationSize(Context c) {
        Resources r = c.getResources();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64, r.getDisplayMetrics());
    }

    public static int getLargeNotificationWidth(Context c) {
        Resources r = c.getResources();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 360, r.getDisplayMetrics());
    }

    public static int convertDpToPixels(Context c, int dp) {
        Resources r = c.getResources();
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
    }

    public static int getActionBarHeight(Context c) {
        int actionBarHeight = 0;
        TypedValue tv = new TypedValue();
        if (c.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true))
            actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, c.getResources().getDisplayMetrics());
        else
            actionBarHeight = 0; // No ActionBar style (pre-Honeycomb or ActionBar not in theme)

        return actionBarHeight;
    }

    public static int getActionBarAndStatusBarHeight(Context c) {
        int actionBarHeight = getActionBarHeight(c);
        int statusBarHeight = ViewUtils.getStatusBarHeight(c);

        // We're only interested in returning the combined
        // height, if we're running on KitKat or above
        return hasKitKat() ?
                actionBarHeight + statusBarHeight : actionBarHeight;
    }

    public static String md5(final String s) {
        try {
            // Create MD5 Hash
            MessageDigest digest = java.security.MessageDigest
                    .getInstance("MD5");
            digest.update(s.getBytes());
            byte messageDigest[] = digest.digest();

            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            for (int i = 0; i < messageDigest.length; i++) {
                String h = Integer.toHexString(0xFF & messageDigest[i]);
                while (h.length() < 2)
                    h = "0" + h;
                hexString.append(h);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static int getThumbnailSize(Context c) {
        final int mImageThumbSize = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
        final int mImageThumbSpacing = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);

        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int numColumns = (int) Math.floor(Math.max(size.x, size.y) / (mImageThumbSize + mImageThumbSpacing));

        if (numColumns > 0) {
            final int columnWidth = (Math.max(size.x, size.y) / numColumns) - mImageThumbSpacing;

            if (columnWidth > 320)
                return 440;
            else if (columnWidth > 240)
                return 320;
            else if (columnWidth > 180)
                return 240;
        }

        return 180;
    }

    public static void resizeBitmapFileToCoverSize(Context c, String filepath) {

        final int mImageThumbSize = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
        final int mImageThumbSpacing = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);

        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int numColumns = (int) Math.floor(Math.max(size.x, size.y) / (mImageThumbSize + mImageThumbSpacing));

        if (numColumns > 0) {
            final int columnWidth = (Math.max(size.x, size.y) / numColumns) - mImageThumbSpacing;

            int imageWidth = 0;

            if (columnWidth > 300)
                imageWidth = 500;
            else if (columnWidth > 240)
                imageWidth = 320;
            else if (columnWidth > 180)
                imageWidth = 240;
            else
                imageWidth = 180;

            if (new File(filepath).exists())
                try {
                    Bitmap bm = decodeSampledBitmapFromFile(filepath, imageWidth, (int) (imageWidth * 1.5));
                    bm = Bitmap.createScaledBitmap(bm, imageWidth, (int) (imageWidth * 1.5), true);
                    FileOutputStream out = new FileOutputStream(filepath);
                    bm.compress(Bitmap.CompressFormat.JPEG, 90, out);
                    out.close();
                    bm.recycle();
                } catch (Exception e) {}
        }
    }

    public static String getImageUrlSize(Context c) {
        final int mImageThumbSize = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
        final int mImageThumbSpacing = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);

        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int numColumns = (int) Math.floor(Math.max(size.x, size.y) / (mImageThumbSize + mImageThumbSpacing));

        if (numColumns > 0) {
            final int columnWidth = (Math.max(size.x, size.y) / numColumns) - mImageThumbSpacing;

            if (columnWidth > 300)
                return "w500";
            else if (columnWidth > 185)
                return "w300";
        }

        return "w185";
    }

    public static String getBackdropUrlSize(Context c) {
        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int width = Math.max(size.x, size.y);

        if (width > 1280 && isTablet(c)) // We only want to download full size images on tablets, as these are the only devices where you can see the difference
            return "original";
        else if (width > 780)
            return "w1280";
        return "w780";
    }

    public static String getBackdropThumbUrlSize(Context c) {
        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int width = Math.min(size.x, size.y);

        if (width >= 780)
            return "w780";
        if (width >= 400)
            return "w500";
        return "w300";
    }

    public static String getActorUrlSize(Context c) {
        final int mImageThumbSize = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
        final int mImageThumbSpacing = c.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);

        WindowManager window = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display d = window.getDefaultDisplay();

        Point size = new Point();
        d.getSize(size);

        final int numColumns = (int) Math.floor(Math.max(size.x, size.y) / (mImageThumbSize + mImageThumbSpacing));

        if (numColumns > 0) {
            final int columnWidth = (Math.max(size.x, size.y) / numColumns) - mImageThumbSpacing;

            if (columnWidth > 400)
                return "h632";

            if (columnWidth >= 300)
                return "w300";
        }

        return "w185";
    }

    public static boolean checkFileTypes(String file) {
        // We don't want to include files that start with ._ or .DS_Store
        if (file.matches("(?i).*[/][\\.](?:_|DS_Store).*[\\.].*$"))
            return false;

        if (file.contains(".")) { // Must have a file type
            String type = file.substring(file.lastIndexOf("."));
            String[] filetypes = allFileTypes.split("\\.");
            int count = filetypes.length;
            for (int i = 0; i < count; i++)
                if (type.toLowerCase(Locale.ENGLISH).equals("." + filetypes[i]))
                    return true;
        }
        return false;
    }

    public static String removeExtension(String filepath) {
        final int lastPeriodPos = filepath.lastIndexOf('.');
        if (lastPeriodPos <= 0) {
            return filepath;
        } else {
            // Remove the last period and everything after it
            return filepath.substring(0, lastPeriodPos);
        }
    }

    public static String convertToGenericNfo(String filepath) {
        final int lastPeriodPos = filepath.lastIndexOf('/');
        if (lastPeriodPos <= 0) {
            return filepath;
        } else {
            // Remove the last period and everything after it
            return filepath.substring(0, lastPeriodPos) + "/movie.nfo";
        }
    }

    /**
     * Returns a blurred bitmap. It uses a RenderScript to blur the bitmap very fast.
     * @param context
     * @param originalBitmap
     * @param radius
     * @return
     */
    public static Bitmap fastBlur(Context context, Bitmap originalBitmap, int radius) {
        final RenderScript rs = RenderScript.create(context);

        final Allocation input = Allocation.createFromBitmap(rs, originalBitmap);
        final Allocation output = Allocation.createTyped(rs, input.getType());
        final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setRadius(radius);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(originalBitmap);

        return originalBitmap;
    }

    public static boolean downloadFile(String url, String savePath) {
        if (TextUtils.isEmpty(url))
            return false;

        InputStream in = null;
        OutputStream fileos = null;

        try {
            int bufferSize = 8192;
            byte[] retVal = null;

            Request request = new Request.Builder()
                    .url(url)
                    .build();

            Response response = MizuuApplication.getOkHttpClient().newCall(request).execute();
            if (!response.isSuccessful())
                return false;

            fileos = new BufferedOutputStream(new FileOutputStream(savePath));
            in = new BufferedInputStream(response.body().byteStream(), bufferSize);

            retVal = new byte[bufferSize];
            int length = 0;
            while((length = in.read(retVal)) > -1) {
                fileos.write(retVal, 0, length);
            }
        } catch(Exception e) {
            // The download failed, so let's delete whatever was downloaded
            deleteFile(new File(savePath));

            return false;
        } finally {
            if (fileos != null) {
                try {
                    fileos.flush();
                    fileos.close();
                } catch (IOException e) {}
            }

            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {}
            }
        }

        return true;
    }

    public static JSONObject getJSONObject(Context context, String url) {
        final OkHttpClient client = MizuuApplication.getOkHttpClient();

        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();

        try {
            Response response = client.newCall(request).execute();

            if (response.code() >= 429) {
                // HTTP error 429 and above means that we've exceeded the query limit
                // for TMDb. Sleep for 5 seconds and try again.
                Thread.sleep(5000);
                response = client.newCall(request).execute();
            }
            return new JSONObject(response.body().string());
        } catch (Exception e) { // IOException and JSONException
            return new JSONObject();
        }
    }

    public static String getStringFromJSONObject(JSONObject json, String name, String fallback) {
        try {
            String s = json.getString(name);
            if (s.equals("null"))
                return fallback;
            return s;
        } catch (Exception e) {
            return fallback;
        }
    }

    public static int getInteger(String number) {
        try {
            return Integer.valueOf(number);
        } catch (NumberFormatException nfe) {
            return 0;
        }
    }

    public static int getInteger(double number) {
        try {
            return (int) number;
        } catch (Exception e) {
            return 0;
        }
    }

    public static String removeIndexZero(String s) {
        if (!TextUtils.isEmpty(s))
            try {
                return String.valueOf(Integer.parseInt(s));
            } catch (NumberFormatException e) {}
        return s;
    }

    public static String addIndexZero(String s) {
        if (TextUtils.isEmpty(s))
            return "00";
        try {
            return String.format(Locale.ENGLISH, "%02d", Integer.parseInt(s));
        } catch (NumberFormatException e) {
            return "00";
        }
    }

    public static String addIndexZero(int number) {
        try {
            return String.format(Locale.ENGLISH, "%02d", number);
        } catch (Exception e) {
            return "00";
        }
    }

    public static String URLEncodeUTF8(String s) {
        return Uri.parse(s).toString();
    }

    public static final int TYPE_MOVIE = 0, TYPE_SHOWS = 1;

    public static ArrayList<FileSource> getFileSources(int type, boolean onlyNetworkSources) {

        ArrayList<FileSource> filesources = new ArrayList<FileSource>();
        DbAdapterSources dbHelperSources = MizuuApplication.getSourcesAdapter();

        Cursor c = null;
        if (type == TYPE_MOVIE)
            c = dbHelperSources.fetchAllMovieSources();
        else
            c = dbHelperSources.fetchAllShowSources();

        ColumnIndexCache cache = new ColumnIndexCache();

        try {
            while (c.moveToNext()) {
                if (onlyNetworkSources) {
                    if (c.getInt(cache.getColumnIndex(c, DbAdapterSources.KEY_FILESOURCE_TYPE)) == FileSource.SMB) {
                        filesources.add(new FileSource(
                                c.getLong(cache.getColumnIndex(c, DbAdapterSources.KEY_ROWID)),
                                c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_FILEPATH)),
                                c.getInt(cache.getColumnIndex(c, DbAdapterSources.KEY_FILESOURCE_TYPE)),
                                c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_USER)),
                                c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_PASSWORD)),
                                c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_DOMAIN)),
                                c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_TYPE))
                        ));
                    }
                } else {
                    filesources.add(new FileSource(
                            c.getLong(cache.getColumnIndex(c, DbAdapterSources.KEY_ROWID)),
                            c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_FILEPATH)),
                            c.getInt(cache.getColumnIndex(c, DbAdapterSources.KEY_FILESOURCE_TYPE)),
                            c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_USER)),
                            c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_PASSWORD)),
                            c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_DOMAIN)),
                            c.getString(cache.getColumnIndex(c, DbAdapterSources.KEY_TYPE))
                    ));
                }
            }
        } catch (Exception e) {
        } finally {
            c.close();
            cache.clear();
        }

        return filesources;
    }

    public static SmbLogin getLoginFromFilesource(FileSource source) {
        if (source == null ||
                TextUtils.isEmpty(source.getDomain()) &&
                        TextUtils.isEmpty(source.getUser()) &&
                        TextUtils.isEmpty(source.getPassword())) {
            return new SmbLogin();
        } else {
            return new SmbLogin(source.getDomain(), source.getUser(), source.getPassword());
        }
    }

    public static SmbLogin getLoginFromFilepath(int type, String filepath) {

        ArrayList<FileSource> filesources = MizLib.getFileSources(type, true);
        FileSource source = null;

        for (int i = 0; i < filesources.size(); i++) {
            if (filepath.contains(filesources.get(i).getFilepath())) {
                source = filesources.get(i);
                break;
            }
        }

        return getLoginFromFilesource(source);
    }

    public static int COVER = 1, BACKDROP = 2;
    public static SmbFile getCustomCoverArt(String filepath, SmbLogin auth, int type) throws MalformedURLException, UnsupportedEncodingException, SmbException {
        String parentPath = filepath.substring(0, filepath.lastIndexOf("/"));
        if (!parentPath.endsWith("/"))
            parentPath += "/";

        String filename = filepath.substring(0, filepath.lastIndexOf(".")).replaceAll("part[1-9]|cd[1-9]", "").trim();

        String[] list = MizuuApplication.getCifsFilesList(parentPath);

        if (list == null) {
            SmbFile s = new SmbFile(createSmbLoginString(
                    auth.getDomain(),
                    auth.getUsername(),
                    auth.getPassword(),
                    parentPath,
                    false));

            try {
                list = s.list();

                MizuuApplication.putCifsFilesList(parentPath, list);
            } catch (Exception e) {
                return null;
            }
        }

        String name = "", absolutePath = "", customCoverArt = "";

        if (type == COVER) {
            for (int i = 0; i < list.length; i++) {
                name = list[i];
                absolutePath = parentPath + list[i];
                if (name.equalsIgnoreCase("poster.jpg") ||
                        name.equalsIgnoreCase("poster.jpeg") ||
                        name.equalsIgnoreCase("poster.tbn") ||
                        name.equalsIgnoreCase("folder.jpg") ||
                        name.equalsIgnoreCase("folder.jpeg") ||
                        name.equalsIgnoreCase("folder.tbn") ||
                        name.equalsIgnoreCase("cover.jpg") ||
                        name.equalsIgnoreCase("cover.jpeg") ||
                        name.equalsIgnoreCase("cover.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-poster.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-poster.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-poster.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-folder.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-folder.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-folder.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-cover.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-cover.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-cover.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + ".jpg") ||
                        absolutePath.equalsIgnoreCase(filename + ".jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + ".tbn")) {
                    customCoverArt = absolutePath;
                    break;
                }
            }
        } else {
            for (int i = 0; i < list.length; i++) {
                name = list[i];
                absolutePath = parentPath + list[i];
                if (name.equalsIgnoreCase("fanart.jpg") ||
                        name.equalsIgnoreCase("fanart.jpeg") ||
                        name.equalsIgnoreCase("fanart.tbn") ||
                        name.equalsIgnoreCase("banner.jpg") ||
                        name.equalsIgnoreCase("banner.jpeg") ||
                        name.equalsIgnoreCase("banner.tbn") ||
                        name.equalsIgnoreCase("backdrop.jpg") ||
                        name.equalsIgnoreCase("backdrop.jpeg") ||
                        name.equalsIgnoreCase("backdrop.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-fanart.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-fanart.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-fanart.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-banner.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-banner.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-banner.tbn") ||
                        absolutePath.equalsIgnoreCase(filename + "-backdrop.jpg") ||
                        absolutePath.equalsIgnoreCase(filename + "-backdrop.jpeg") ||
                        absolutePath.equalsIgnoreCase(filename + "-backdrop.tbn")) {
                    customCoverArt = absolutePath;
                    break;
                }
            }
        }

        if (!TextUtils.isEmpty(customCoverArt))
            return new SmbFile(createSmbLoginString(
                    auth.getDomain(),
                    auth.getUsername(),
                    auth.getPassword(),
                    customCoverArt,
                    false));

        return null;
    }

    public static String[] subtitleFormats = new String[]{".srt", ".sub", ".ssa", ".ssf", ".smi", ".txt", ".usf", ".ass", ".stp", ".idx", ".aqt", ".cvd", ".dks", ".jss", ".mpl", ".pjs", ".psb", ".rt", ".svcd", ".usf"};

    public static boolean isSubtitleFile(String s) {
        int count = subtitleFormats.length;
        for (int i = 0; i < count; i++)
            if (s.endsWith(subtitleFormats[i]))
                return true;
        return false;
    }

    public static List<SmbFile> getSubtitleFiles(String filepath, SmbLogin auth) throws MalformedURLException, UnsupportedEncodingException {
        ArrayList<SmbFile> subs = new ArrayList<SmbFile>();

        String fileType = "";
        if (filepath.contains(".")) {
            fileType = filepath.substring(filepath.lastIndexOf("."));
        }

        int count = subtitleFormats.length;
        for (int i = 0; i < count; i++) {
            subs.add(new SmbFile(createSmbLoginString(
                    auth.getDomain(),
                    auth.getUsername(),
                    auth.getPassword(),
                    filepath.replace(fileType, subtitleFormats[i]),
                    false)));
        }

        return subs;
    }

    /**
     * A bit of a hack to properly delete files / folders from the OS
     * @param f
     * @return
     */
    public static boolean deleteFile(File f) {
        return f.delete();
    }

    public static String createSmbLoginString(String domain, String user, String password, String server, boolean isFolder) {
        // Create the string to fit the following syntax: smb://[[[domain;]username[:password]@]server[:port]/
        StringBuilder sb = new StringBuilder("smb://");

        try {
            user = URLEncoder.encode(user, "utf-8");
        } catch (UnsupportedEncodingException e) {}
        try {
            password = URLEncoder.encode(password, "utf-8");
        } catch (UnsupportedEncodingException e) {}
        try {
            domain = URLEncoder.encode(domain, "utf-8");
        } catch (UnsupportedEncodingException e) {}

        user = user.replace("+", "%20");
        password = password.replace("+", "%20");
        domain = domain.replace("+", "%20");
        server = server.replace("smb://", "");

        // Only add domain, username and password details if the username isn't empty
        if (!TextUtils.isEmpty(user)) {
            // Add the domain details
            if (!TextUtils.isEmpty(domain))
                sb.append(domain).append(";");

            // Add username
            sb.append(user);

            // Add password
            if (!TextUtils.isEmpty(password))
                sb.append(":").append(password);

            sb.append("@");
        }

        sb.append(server);

        if (isFolder)
            if (!server.endsWith("/"))
                sb.append("/");

        return sb.toString();
    }

    public static void deleteShow(Context c, TvShow thisShow, boolean showToast) {
        // Create and open database
        DbAdapterTvShows dbHelper = MizuuApplication.getTvDbAdapter();
        boolean deleted = dbHelper.deleteShow(thisShow.getId());

        DbAdapterTvShowEpisodes db = MizuuApplication.getTvEpisodeDbAdapter();
        deleted = deleted && db.deleteAllEpisodes(thisShow.getId());

        if (deleted) {
            try {
                // Delete cover art image
                File coverArt = thisShow.getCoverPhoto();
                if (coverArt.exists() && coverArt.getAbsolutePath().contains("com.miz.mizuu")) {
                    MizLib.deleteFile(coverArt);
                }

                // Delete thumbnail image
                File thumbnail = thisShow.getThumbnail();
                if (thumbnail.exists() && thumbnail.getAbsolutePath().contains("com.miz.mizuu")) {
                    MizLib.deleteFile(thumbnail);
                }

                // Delete backdrop image
                File backdrop = new File(thisShow.getBackdrop());
                if (backdrop.exists() && backdrop.getAbsolutePath().contains("com.miz.mizuu")) {
                    MizLib.deleteFile(backdrop);
                }

                // Delete episode images
                File dataFolder = MizuuApplication.getTvShowEpisodeFolder(c);
                File[] listFiles = dataFolder.listFiles();
                if (listFiles != null) {
                    int count = listFiles.length;
                    for (int i = 0; i < count; i++)
                        if (listFiles[i].getName().startsWith(thisShow.getId() + "_S"))
                            MizLib.deleteFile(listFiles[i]);
                }
            } catch (NullPointerException e) {} // No file to delete
        } else {
            if (showToast)
                Toast.makeText(c, c.getString(R.string.failedToRemoveShow), Toast.LENGTH_SHORT).show();
        }
    }

    public static String getYouTubeId(String url) {
        String pattern = "https?:\\/\\/(?:[0-9A-Z-]+\\.)?(?:youtu\\.be\\/|youtube\\.com\\S*[^\\w\\-\\s])([\\w\\-]{11})(?=[^\\w\\-]|$)(?![?=&+%\\w]*(?:['\"][^<>]*>|<\\/a>))[?=&+%\\w]*";

        Pattern compiledPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
        Matcher matcher = compiledPattern.matcher(url);

        if (matcher.find(1))
            return getYTId(matcher.group(1));

        if (matcher.find(0))
            return getYTId(matcher.group(0));

        return url;
    }

    private static String getYTId(String url) {
        String result = url;

        if (result.contains("v=")) {
            result = result.substring(result.indexOf("v=") + 2);

            if (result.contains("&")) {
                result = result.substring(0, result.indexOf("&"));
            }
        }

        if (result.contains("youtu.be/")) {
            result = result.substring(result.indexOf("youtu.be/") + 9);

            if (result.contains("&")) {
                result = result.substring(0, result.indexOf("&"));
            }
        }

        return result;
    }

    private static String convertToHex(byte[] data) {
        StringBuilder buf = new StringBuilder();
        int count = data.length;
        for (int i = 0; i < count; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            do {
                buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
                halfbyte = data[i] & 0x0F;
            } while (two_halfs++ < 1);
        }
        return buf.toString();
    }

    public static String SHA1(String text) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(text.getBytes("iso-8859-1"), 0, text.length());
            byte[] sha1hash = md.digest();
            return convertToHex(sha1hash);
        } catch (Exception e) {
            return "";
        }
    }

    public static Request getJsonPostRequest(String url, JSONObject holder) {
        return new Request.Builder()
                .url(url)
                .addHeader("Content-type", "application/json")
                .post(RequestBody.create(MediaType.parse("application/json"), holder.toString()))
                .build();
    }

    public static Request getTraktAuthenticationRequest(String url, String username, String password) throws JSONException {
        JSONObject holder = new JSONObject();
        holder.put("username", username);
        holder.put("password", password);

        return new Request.Builder()
                .url(url)
                .addHeader("Content-type", "application/json")
                .post(RequestBody.create(MediaType.parse("application/json"), holder.toString()))
                .build();
    }

    public static boolean isMovieLibraryBeingUpdated(Context c) {
        ActivityManager manager = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningServiceInfo> services = manager.getRunningServices(Integer.MAX_VALUE);
        int count = services.size();
        for (int i = 0; i < count; i++) {
            if (MovieLibraryUpdate.class.getName().equals(services.get(i).service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    public static boolean isTvShowLibraryBeingUpdated(Context c) {
        ActivityManager manager = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningServiceInfo> services = manager.getRunningServices(Integer.MAX_VALUE);
        int count = services.size();
        for (int i = 0; i < count; i++) {
            if (TvShowsLibraryUpdate.class.getName().equals(services.get(i).service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    public static final int HEIGHT = 100, WIDTH = 110;

    public static int getDisplaySize(Context c, int type) {
        WindowManager wm = (WindowManager) c.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();

        Point size = new Point();
        display.getSize(size);

        return type == HEIGHT ?
                size.y : size.x;
    }

    public static int getFileSizeLimit(Context c) {
        return 25 * 1024 * 1024; // 25 MB
    }

    public static String getTraktUserName(Context c) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(c);
        return settings.getString(TRAKT_USERNAME, "").trim();
    }

    public static String removeWikipediaNotes(String original) {
        original = original.trim().replaceAll("(?i)from wikipedia, the free encyclopedia.", "").replaceAll("(?i)from wikipedia, the free encyclopedia", "");
        original = original.replaceAll("(?m)^[ \t]*\r?\n", "");
        if (original.contains("Description above from the Wikipedia")) {
            original = original.substring(0, original.lastIndexOf("Description above from the Wikipedia"));
        }

        return original.trim();
    }

    public static String getParentFolder(String filepath) {
        try {
            return filepath.substring(0, filepath.lastIndexOf("/"));
        } catch (Exception e) {
            return "";
        }
    }

    public static void scheduleMovieUpdate(Context context) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);

        // Check if scheduled updates are enabled, and schedule the next update if this is the case
        if (settings.getInt(SCHEDULED_UPDATES_MOVIE, ScheduledUpdatesFragment.NOT_ENABLED) > ScheduledUpdatesFragment.AT_LAUNCH) {
            ScheduledUpdatesAlarmManager.cancelUpdate(ScheduledUpdatesAlarmManager.MOVIES, context);
            long duration = MizLib.HOUR;
            switch (settings.getInt(SCHEDULED_UPDATES_MOVIE, ScheduledUpdatesFragment.NOT_ENABLED)) {
                case ScheduledUpdatesFragment.EVERY_2_HOURS:
                    duration = MizLib.HOUR * 2;
                    break;
                case ScheduledUpdatesFragment.EVERY_4_HOURS:
                    duration = MizLib.HOUR * 4;
                    break;
                case ScheduledUpdatesFragment.EVERY_6_HOURS:
                    duration = MizLib.HOUR * 6;
                    break;
                case ScheduledUpdatesFragment.EVERY_12_HOURS:
                    duration = MizLib.HOUR * 12;
                    break;
                case ScheduledUpdatesFragment.EVERY_DAY:
                    duration = MizLib.DAY;
                    break;
                case ScheduledUpdatesFragment.EVERY_WEEK:
                    duration = MizLib.WEEK;
                    break;
            }
            ScheduledUpdatesAlarmManager.startUpdate(ScheduledUpdatesAlarmManager.MOVIES, context, duration);
        }
    }

    public static void scheduleShowsUpdate(Context context) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);

        // Check if scheduled updates are enabled, and schedule the next update if this is the case
        if (settings.getInt(SCHEDULED_UPDATES_TVSHOWS, ScheduledUpdatesFragment.NOT_ENABLED) > ScheduledUpdatesFragment.AT_LAUNCH) {
            ScheduledUpdatesAlarmManager.cancelUpdate(ScheduledUpdatesAlarmManager.SHOWS, context);
            long duration = MizLib.HOUR;
            switch (settings.getInt(SCHEDULED_UPDATES_TVSHOWS, ScheduledUpdatesFragment.NOT_ENABLED)) {
                case ScheduledUpdatesFragment.EVERY_2_HOURS:
                    duration = MizLib.HOUR * 2;
                    break;
                case ScheduledUpdatesFragment.EVERY_4_HOURS:
                    duration = MizLib.HOUR * 4;
                    break;
                case ScheduledUpdatesFragment.EVERY_6_HOURS:
                    duration = MizLib.HOUR * 6;
                    break;
                case ScheduledUpdatesFragment.EVERY_12_HOURS:
                    duration = MizLib.HOUR * 12;
                    break;
                case ScheduledUpdatesFragment.EVERY_DAY:
                    duration = MizLib.DAY;
                    break;
                case ScheduledUpdatesFragment.EVERY_WEEK:
                    duration = MizLib.WEEK;
                    break;
            }
            ScheduledUpdatesAlarmManager.startUpdate(ScheduledUpdatesAlarmManager.SHOWS, context, duration);
        }
    }

    public static int countOccurrences(String haystack, char needle) {
        int count = 0;
        char[] array = haystack.toCharArray();
        for (int i = 0; i < array.length; i++) {
            if (array[i] == needle) {
                ++count;
            }
        }
        return count;
    }

    public static String decryptImdbId(String filename) {
        Pattern p = Pattern.compile("(tt\\d{7})");
        Matcher m = p.matcher(filename);
        if (m.find())
            return m.group(1);
        return null;
    }

    public static String decryptName(String input, String customTags) {
        if (TextUtils.isEmpty(input))
            return "";

        String output = getNameFromFilename(input);
        output = fixAbbreviations(output);

        // Used to remove [SET {title}] from the beginning of filenames
        if (output.startsWith("[") && output.contains("]")) {
            String after = "";

            if (output.matches("(?i)^\\[SET .*\\].*?")) {
                try {
                    after = output.substring(output.indexOf("]") + 1, output.length());
                } catch (Exception e) {}
            }

            if (!TextUtils.isEmpty(after))
                output = after;
        }

        output = output.replaceAll(WAREZ_PATTERN + "|\\)|\\(|\\[|\\]|\\{|\\}|\\'|\\<|\\>|\\-", "");

        // Improved support for French titles that start with C' or L'
        if (output.matches("(?i)^(c|l)(\\_|\\.)\\w.*?")) {
            StringBuilder sb = new StringBuilder(output);
            sb.replace(1, 2, "'");
            output = sb.toString();
        }

        if (!TextUtils.isEmpty(customTags)) {
            String[] custom = customTags.split("<MiZ>");
            int count = custom.length;
            for (int i = 0; i < count; i++)
                try {
                    output = output.replaceAll("(?i)" + custom[i], "");
                } catch (Exception e) {}
        }

        output = output.replaceAll("\\s\\-\\s|\\.|\\,|\\_", " "); // Remove separators
        output = output.trim().replaceAll("(?i)(part)$", ""); // Remove "part" in the end of the string
        output = output.trim().replaceAll("(?i)(?:s|season[ ._-]*)\\d{1,4}.*", ""); // Remove "season####" in the end of the string

        return output.replaceAll(" +", " ").trim(); // replaceAll() needed to remove all instances of multiple spaces
    }

    private final static String YEAR_PATTERN = "(18|19|20)[0-9][0-9]";
    private final static String WAREZ_PATTERN = "(?i)(dvdscreener|dvdscreen|dvdscr|dvdrip|dvd5|dvd|xvid|divx|m\\-480p|m\\-576p|m\\-720p|m\\-864p|m\\-900p|m\\-1080p|m480p|m576p|m720p|m864p|m900p|m1080p|480p|576p|720p|864p|900p|1080p|1080i|720i|mhd|brrip|bdrip|brscreener|brscreen|brscr|aac|x264|bluray|dts|screener|hdtv|ac3|repack|2\\.1|5\\.1|ac3_6|7\\.1|h264|hdrip|ntsc|proper|readnfo|rerip|subbed|vcd|scvd|pdtv|sdtv|hqts|hdcam|multisubs|650mb|700mb|750mb|webdl|web-dl|bts|korrip|webrip|korsub|1link|sample|tvrip|tvr|extended.editions?|directors cut|tfe|unrated|\\(.*?torrent.*?\\)|\\[.*?\\]|\\(.*?\\)|\\{.*?\\}|part[0-9]|cd[0-9])";
    private final static String ABBREVIATION_PATTERN = "(?<=(^|[.])[\\S&&\\D])[.](?=[\\S&&\\D]([.]|$))";

    public static String getNameFromFilename(String input) {
        int lastIndex = 0;

        Pattern searchPattern = Pattern.compile(YEAR_PATTERN);
        Matcher searchMatcher = searchPattern.matcher(input);

        while (searchMatcher.find())
            lastIndex = searchMatcher.end();

        if (lastIndex > 0)
            try {
                return input.substring(0, lastIndex - 4);
            } catch (Exception e) {}

        return input;
    }

    public static String fixAbbreviations(String input) {
        return input.replaceAll(ABBREVIATION_PATTERN, "");
    }

    public static String decryptYear(String input) {
        String result = "";
        Pattern searchPattern = Pattern.compile(YEAR_PATTERN);
        Matcher searchMatcher = searchPattern.matcher(input);

        while (searchMatcher.find()) {
            try {
                int lastIndex = searchMatcher.end();
                result = input.substring(lastIndex - 4, lastIndex);
            } catch (Exception e) {}
        }

        return result;
    }

    /**
     * Decode and sample down a bitmap from a file to the requested width and height.
     *
     * @param filename The full path of the file to decode
     * @param reqWidth The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     *         that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmapFromFile(String filename,
                                                     int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filename, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inMutable = true;
        options.inPreferredConfig = Config.RGB_565;

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(filename, options);
    }

    /**
     * Decode and sample down a bitmap from resources to the requested width and height.
     *
     * @param res The resources object containing the image data
     * @param resId The resource id of the image data
     * @param reqWidth The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     *         that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    /**
     * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
     * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
     * the closest inSampleSize that will result in the final decoded bitmap having a width and
     * height equal to or larger than the requested width and height. This implementation does not
     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
     * results in a larger bitmap which isn't as useful for caching purposes.
     *
     * @param options An options object with out* params already populated (run through a decode*
     *            method with inJustDecodeBounds==true
     * @param reqWidth The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return The value to be used for inSampleSize
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
                                            int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
            // with both dimensions larger than or equal to the requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

            // This offers some additional logic in case the image has a strange
            // aspect ratio. For example, a panorama may have a much larger
            // width than height. In these cases the total pixels might still
            // end up being too large to fit comfortably in memory, so we should
            // be more aggressive with sample down the image (=larger inSampleSize).

            final float totalPixels = width * height;

            // Anything more than 2x the requested pixels we'll sample down further
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }

    @SuppressWarnings("deprecation")
    public static long getFreeMemory() {
        StatFs stat = new StatFs(Environment.getDataDirectory().getPath());
        if (hasJellyBeanMR2())
            return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
        else
            return stat.getAvailableBlocks() * stat.getBlockSize();
    }

    private static String[] MEDIA_APPS = new String[]{"com.imdb.mobile", "com.google.android.youtube", "com.ted.android", "com.google.android.videos", "se.mtg.freetv.tv3_dk", "tv.twitch.android.viewer",
            "com.netflix.mediaclient", "com.gotv.crackle.handset", "net.flixster.android", "com.google.tv.alf", "com.viki.android", "com.mobitv.client.mobitv", "com.hulu.plus.jp", "com.hulu.plus",
            "com.mobitv.client.tv", "air.com.vudu.air.DownloaderTablet", "com.hbo.android.app", "com.HBO", "bbc.iplayer.android", "air.uk.co.bbc.android.mediaplayer", "com.rhythmnewmedia.tvdotcom",
            "com.cnettv.app", "com.xfinity.playnow"};

    public static boolean isMediaApp(ApplicationInfo ai) {
        for (int i = 0; i < MEDIA_APPS.length; i++)
            if (MEDIA_APPS[i].equals(ai.packageName))
                return true;
        return false;
    }

    private static int mRuntimeInMinutes;
    public static String getRuntimeInMinutesOrHours(String runtime, String hour, String minute) {
        mRuntimeInMinutes = Integer.valueOf(runtime);
        if (mRuntimeInMinutes >= 60) {
            return (mRuntimeInMinutes / 60) + hour;
        }
        return mRuntimeInMinutes + minute;
    }

    public static int getPartNumberFromFilepath(String filepath) {
        if (filepath.matches(".*part[1-9].*"))
            filepath = filepath.substring(filepath.lastIndexOf("part") + 4, filepath.length());
        else if (filepath.matches(".*cd[1-9].*"))
            filepath = filepath.substring(filepath.lastIndexOf("cd") + 2, filepath.length());

        filepath = filepath.substring(0, 1);

        try {
            return Integer.valueOf(filepath);
        } catch (NumberFormatException nfe) { return 0; }
    }

    public static List<String> getSplitParts(String filepath, SmbLogin auth) throws MalformedURLException, UnsupportedEncodingException, SmbException {
        ArrayList<String> parts = new ArrayList<String>();

        String fileType = "";
        if (filepath.contains(".")) {
            fileType = filepath.substring(filepath.lastIndexOf("."));
        }

        if (filepath.matches(".*part[1-9].*"))
            filepath = filepath.substring(0, filepath.lastIndexOf("part") + 4);
        else if (filepath.matches(".*cd[1-9].*"))
            filepath = filepath.substring(0, filepath.lastIndexOf("cd") + 2);

        if (auth == null) { // Check if it's a local file
            File temp;
            for (int i = 1; i < 10; i++) {
                temp = new File(filepath + i + fileType);
                if (temp.exists())
                    parts.add(temp.getAbsolutePath());
            }
        } else { // It's a network file
            SmbFile temp;
            for (int i = 1; i < 10; i++) {
                temp = new SmbFile(createSmbLoginString(
                        auth.getDomain(),
                        auth.getUsername(),
                        auth.getPassword(),
                        filepath + i + fileType,
                        false));
                if (temp.exists())
                    parts.add(temp.getPath());
            }
        }

        return parts;
    }

    public static String transformSmbPath(String smbPath) {
        if (smbPath.contains("smb") && smbPath.contains("@"))
            return "smb://" + smbPath.substring(smbPath.indexOf("@") + 1);
        return smbPath.replace("/smb:/", "smb://");
    }

    public static int getNavigationBarHeight(Context context) {
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
        if (resourceId > 0) {
            return resources.getDimensionPixelSize(resourceId);
        }
        return 0;
    }

    public static int getNavigationBarWidth(Context context) {
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android");
        if (resourceId > 0) {
            return resources.getDimensionPixelSize(resourceId);
        }
        return 0;
    }

    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap
                .getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);
        final float roundPx = pixels;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }

    public static File getRandomBackdropFile(Context c) {
        ArrayList<File> files = new ArrayList<File>();

        File[] f = MizuuApplication.getMovieBackdropFolder(c).listFiles();
        if (f != null)
            Collections.addAll(files, f);

        f = MizuuApplication.getTvShowBackdropFolder(c).listFiles();
        if (f != null)
            Collections.addAll(files, f);

        if (files.size() > 0) {
            Random rndm = new Random();
            return files.get(rndm.nextInt(files.size()));
        }

        return new File("");
    }

    public static boolean isValidFilename(String name) {
        return !(name.startsWith(".") && MizLib.getCharacterCountInString(name, '.') == 1) && !name.startsWith("._");
    }

    public static boolean exists(String URLName){
        try {
            HttpURLConnection.setFollowRedirects(false);
            HttpURLConnection con =
                    (HttpURLConnection) new URL(URLName).openConnection();
            con.setRequestMethod("HEAD");
            con.setConnectTimeout(10000);
            return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
        }
        catch (Exception e) {
            return false;
        }
    }

    /**
     * Determines if the device uses navigation controls as the primary navigation from a number of factors.
     * @param context Application Context
     * @return True if the device uses navigation controls, false otherwise.
     */
    public static boolean usesNavigationControl(Context context) {
        Configuration configuration = context.getResources().getConfiguration();
        if (configuration.navigation == Configuration.NAVIGATION_NONAV) {
            return false;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_FINGER) {
            return false;
        } else if (configuration.navigation == Configuration.NAVIGATION_DPAD) {
            return true;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
            return true;
        } else if (configuration.touchscreen == Configuration.TOUCHSCREEN_UNDEFINED) {
            return true;
        } else if (configuration.navigationHidden == Configuration.NAVIGATIONHIDDEN_YES) {
            return true;
        } else if (configuration.uiMode == Configuration.UI_MODE_TYPE_TELEVISION) {
            return true;
        }
        return false;
    }

    public static int getFileSize(URL url) {
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("HEAD");
            conn.getInputStream();
            return conn.getContentLength();
        } catch (IOException e) {
            return -1;
        } finally {
            conn.disconnect();
        }
    }

    public static String getPrettyTime(Context context, int minutes) {
        if (minutes == 0)
            return context.getString(R.string.stringNA);;
        try {
            int hours = (minutes / 60);
            minutes = (minutes % 60);
            String hours_string = hours + " " + context.getResources().getQuantityString(R.plurals.hour, hours, hours);
            String minutes_string = minutes + " " + context.getResources().getQuantityString(R.plurals.minute, minutes, minutes);
            if (hours > 0) {
                if (minutes == 0)
                    return hours_string;
                else
                    return hours_string + " " + minutes_string;
            } else {
                return minutes_string;
            }
        } catch (Exception e) { // Fall back if something goes wrong
            if (minutes > 0)
                return String.valueOf(minutes);
            return context.getString(R.string.stringNA);
        }
    }

    public static String getPrettyRuntime(Context context, int minutes) {
        if (minutes == 0) {
            return context.getString(R.string.stringNA);
        }

        int hours = (minutes / 60);
        minutes = (minutes % 60);

        if (hours > 0) {
            if (minutes == 0) {
                return hours + " " + context.getResources().getQuantityString(R.plurals.hour, hours, hours);
            } else {
                return hours + " " + context.getResources().getQuantityString(R.plurals.hour_short, hours, hours) + " " + minutes + " " + context.getResources().getQuantityString(R.plurals.minute_short, minutes, minutes);
            }
        } else {
            return minutes + " " + context.getResources().getQuantityString(R.plurals.minute, minutes, minutes);
        }
    }

    public static String getPrettyDate(Context context, String date) {
        if (!TextUtils.isEmpty(date)) {
            try {
                String[] dateArray = date.split("-");
                Calendar cal = Calendar.getInstance();
                cal.set(Integer.parseInt(dateArray[0]), Integer.parseInt(dateArray[1]) - 1, Integer.parseInt(dateArray[2]));

                return MizLib.toCapitalFirstChar(cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()) + " " + cal.get(Calendar.YEAR));
            } catch (Exception e) { // Fall back if something goes wrong
                return date;
            }
        } else {
            return context.getString(R.string.stringNA);
        }
    }

    public static String getPrettyDatePrecise(Context context, String date) {
        if (!TextUtils.isEmpty(date)) {
            try {
                String[] dateArray = date.split("-");
                Calendar cal = Calendar.getInstance();
                cal.set(Integer.parseInt(dateArray[0]), Integer.parseInt(dateArray[1]) - 1, Integer.parseInt(dateArray[2]));

                return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(cal.getTime());
            } catch (Exception e) { // Fall back if something goes wrong
                return date;
            }
        } else {
            return context.getString(R.string.stringNA);
        }
    }

    public static String getPrettyDate(Context context, long millis) {
        if (millis > 0) {
            try {
                Calendar cal = Calendar.getInstance();
                cal.setTimeInMillis(millis);

                return DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(cal.getTime());
            } catch (Exception e) { // Fall back if something goes wrong
                return String.valueOf(millis);
            }
        } else {
            return context.getString(R.string.stringNA);
        }
    }

    public static Comparator<WebMovie> getWebMovieDateComparator() {
        return new Comparator<WebMovie>() {
            @Override
            public int compare(WebMovie o1, WebMovie o2) {
                // Dates are always presented as YYYY-MM-DD, so removing
                // the hyphens will easily provide a great way of sorting.

                int firstDate = 0, secondDate = 0;
                String first = "", second = "";

                if (o1.getRawDate() != null)
                    first = o1.getRawDate().replace("-", "");

                if (!TextUtils.isEmpty(first))
                    firstDate = Integer.valueOf(first);

                if (o2.getRawDate() != null)
                    second = o2.getRawDate().replace("-", "");

                if (!TextUtils.isEmpty(second))
                    secondDate = Integer.valueOf(second);

                // This part is reversed to get the highest numbers first
                if (firstDate < secondDate)
                    return 1; // First date is lower than second date - put it second
                else if (firstDate > secondDate)
                    return -1; // First date is greater than second date - put it first

                return 0; // They're equal
            }
        };
    }

    private static String[] mAdultKeywords = new String[]{"adult", "sex", "porn", "explicit", "penis", "vagina", "asshole",
            "blowjob", "cock", "fuck", "dildo", "kamasutra", "masturbat", "squirt", "slutty", "cum", "cunt"};

    public static boolean isAdultContent(Context context, String title) {
        // Check if the user has enabled adult content - if so, nothing should
        // be blocked and the method should return false regardless of the title
        if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(INCLUDE_ADULT_CONTENT, false))
            return false;

        String lowerCase = title.toLowerCase(Locale.getDefault());

        // Run through the keywords and check
        for (int i = 0; i < mAdultKeywords.length; i++)
            if (lowerCase.contains(mAdultKeywords[i]))
                return true;

        // Certain titles include "XXX" (all caps), so test this against the normal-case title as a last check
        return title.contains("XXX");
    }

    public static boolean isNumber(String runtime) {
        return TextUtils.isDigitsOnly(runtime);
    }

    public static boolean isValidTmdbId(String id) {
        return !TextUtils.isEmpty(id) && !id.equals(DbAdapterMovies.UNIDENTIFIED_ID) && isNumber(id);
    }

    /**
     * Helper method to remove a ViewTreeObserver correctly, i.e.
     * avoiding the deprecated method on API level 16+.
     * @param vto
     * @param victim
     */
    @SuppressWarnings("deprecation")
    public static void removeViewTreeObserver(ViewTreeObserver vto, OnGlobalLayoutListener victim) {
        if (MizLib.hasJellyBean()) {
            vto.removeOnGlobalLayoutListener(victim);
        } else {
            vto.removeGlobalOnLayoutListener(victim);
        }
    }

    public static String getTmdbImageBaseUrl(Context context) {
        long time = PreferenceManager.getDefaultSharedPreferences(context).getLong(TMDB_BASE_URL_TIME, 0);
        long currentTime = System.currentTimeMillis();

        // We store the TMDb base URL for 24 hours
        if (((currentTime - time) < DAY && PreferenceManager.getDefaultSharedPreferences(context).contains(TMDB_BASE_URL)) |
                Looper.getMainLooper().getThread() == Thread.currentThread()) {
            return PreferenceManager.getDefaultSharedPreferences(context).getString(TMDB_BASE_URL, "");
        }

        try {
            JSONObject configuration = getJSONObject(context, "https://api.themoviedb.org/3/configuration?api_key=" + getTmdbApiKey(context));
            String baseUrl = configuration.getJSONObject("images").getString("secure_base_url");

            Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
            editor.putString(TMDB_BASE_URL, baseUrl);
            editor.putLong(TMDB_BASE_URL_TIME, System.currentTimeMillis());
            editor.commit();

            return baseUrl;
        } catch (JSONException e) {
            return null;
        }
    }

    public static void showSelectFileDialog(Context context, ArrayList<Filepath> paths, final Dialog.OnClickListener listener) {
        String[] items = new String[paths.size()];
        for (int i = 0; i < paths.size(); i++)
            items[i] = paths.get(i).getFilepath();

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(context.getString(R.string.selectFile));
        builder.setItems(items, listener);
        builder.show();
    }

    public static String getFilenameWithoutExtension(String filename) {
        try {
            return filename.substring(0, filename.lastIndexOf("."));
        } catch (IndexOutOfBoundsException e) {
            return filename;
        }
    }

    public static void copyDatabase(Context context) {
        try {
            FileUtils.copyFile(context.getDatabasePath("mizuu_data"), new File(Environment.getExternalStorageDirectory(), "mizuu_data.db"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
} public class MizuuApplication extends Application {

	private static DbAdapterTvShows sDbTvShow;
	private static DbAdapterTvShowEpisodes sDbTvShowEpisode;
	private static DbAdapterTvShowEpisodeMappings sDbTvShowEpisodeMappings;
	private static DbAdapterSources sDbSources;
	private static DbAdapterMovies sDbMovies;
	private static DbAdapterMovieMappings sDbMovieMapping;
	private static DbAdapterCollections sDbCollections;
	private static HashMap<String, String[]> sMap = new HashMap<String, String[]>();
	private static Picasso sPicasso;
	private static HashMap<String, Typeface> sTypefaces = new HashMap<String, Typeface>();
	private static HashMap<String, Palette> sPalettes = new HashMap<String, Palette>();
	private static Bus sBus;
	private static File sBaseAppFolder, sMovieThumbFolder, sMovieBackdropFolder, sTvShowThumbFolder, sTvShowBackdropFolder, sTvShowEpisodeFolder, sTvShowSeasonFolder, sAvailableOfflineFolder, sCacheFolder;
	private static Context mInstance;
	private static ArrayListMultimap<String, String> mMovieFilepaths;
	private static OkHttpClient mOkHttpClient;

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

		mInstance = this;

		jcifs.Config.setProperty("jcifs.smb.client.disablePlainTextPasswords", "false");

		// Initialize the preferences
		initializePreferences();

		// Database setup
		sDbMovies = new DbAdapterMovies(this);
		sDbMovieMapping = new DbAdapterMovieMappings(this);
		sDbTvShowEpisode = new DbAdapterTvShowEpisodes(this);
		sDbTvShow = new DbAdapterTvShows(this);
		sDbTvShowEpisodeMappings = new DbAdapterTvShowEpisodeMappings(this);
		sDbSources = new DbAdapterSources(this);
		sDbCollections = new DbAdapterCollections(this);

		getMovieThumbFolder(this);
		getMovieBackdropFolder(this);
		getTvShowThumbFolder(this);
		getTvShowBackdropFolder(this);
		getTvShowEpisodeFolder(this);
		getTvShowSeasonFolder(this);
		getAvailableOfflineFolder(this);

		transitionLocalizationPreference();
	}

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

		sDbTvShow.close();
		sDbTvShowEpisode.close();
		sDbSources.close();
		sDbMovies.close();
		sDbMovieMapping.close();
		sDbCollections.close();
	}

	public static Context getContext() {
		return mInstance;
	}

	private void initializePreferences() {
		PreferenceManager.setDefaultValues(this, R.xml.advanced_prefs, false);
		PreferenceManager.setDefaultValues(this, R.xml.general_prefs, false);
		PreferenceManager.setDefaultValues(this, R.xml.identification_search_prefs, false);
		PreferenceManager.setDefaultValues(this, R.xml.other_prefs, false);
		PreferenceManager.setDefaultValues(this, R.xml.user_interface_prefs, false);
	}

	private void transitionLocalizationPreference() {
		// Transition from the old localization preference if such exists
		String languagePref;
		boolean oldLocalizedPref = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("prefsUseLocalData", false);
		if (oldLocalizedPref) {
			languagePref = Locale.getDefault().getLanguage();
			PreferenceManager.getDefaultSharedPreferences(this).edit().remove("prefsUseLocalData").commit();
		} else {
			languagePref = PreferenceManager.getDefaultSharedPreferences(this).getString(LANGUAGE_PREFERENCE, "en");
		}

		PreferenceManager.getDefaultSharedPreferences(this).edit().putString(LANGUAGE_PREFERENCE, languagePref).commit();
	}

	public static DbAdapterTvShows getTvDbAdapter() {
		return sDbTvShow;
	}

	public static DbAdapterTvShowEpisodes getTvEpisodeDbAdapter() {
		return sDbTvShowEpisode;
	}

	public static DbAdapterTvShowEpisodeMappings getTvShowEpisodeMappingsDbAdapter() {
		return sDbTvShowEpisodeMappings;
	}

	public static DbAdapterSources getSourcesAdapter() {
		return sDbSources;
	}

	public static DbAdapterMovies getMovieAdapter() {
		return sDbMovies;
	}

	public static DbAdapterMovieMappings getMovieMappingAdapter() {
		return sDbMovieMapping;
	}

	public static DbAdapterCollections getCollectionsAdapter() {
		return sDbCollections;
	}

	public static String[] getCifsFilesList(String parentPath) {
		return sMap.get(parentPath);
	}

	public static void putCifsFilesList(String parentPath, String[] list) {
		if (!sMap.containsKey(parentPath))
			sMap.put(parentPath, list);
	}

	public static Picasso getPicasso(Context context) {
		if (sPicasso == null)
			sPicasso = Picasso.with(context);
		return sPicasso;
	}

	public static Bitmap.Config getBitmapConfig() {
		return Bitmap.Config.RGB_565;
	}

	public static Typeface getOrCreateTypeface(Context context, String key) {
		if (!sTypefaces.containsKey(key))
			sTypefaces.put(key, Typeface.createFromAsset(context.getAssets(), key));
		return sTypefaces.get(key);
	}

	public static Palette getPalette(String key) {
		return sPalettes.get(key);
	}

	public static void addToPaletteCache(String key, Palette palette) {
		sPalettes.put(key, palette);
	}

	public static void setupTheme(Context context) {
		context.setTheme(R.style.Mizuu_Theme);
	}

	public static Bus getBus() {
		if (sBus == null)
			sBus = new Bus();
		return sBus;
	}

	public static File getAppFolder(Context c) {
		if (sBaseAppFolder == null) {
			sBaseAppFolder = c.getExternalFilesDir(null);
		}
		return sBaseAppFolder;
	}

	private static File getSubAppFolder(Context c, String foldername) {
		return new File(getAppFolder(c), foldername);
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getMovieThumbFolder(Context c) {
		if (sMovieThumbFolder == null) {
			sMovieThumbFolder = getSubAppFolder(c, "movie-thumbs");
			sMovieThumbFolder.mkdirs();
		}
		return sMovieThumbFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getMovieBackdropFolder(Context c) {
		if (sMovieBackdropFolder == null) {
			sMovieBackdropFolder = getSubAppFolder(c, "movie-backdrops");
			sMovieBackdropFolder.mkdirs();
		}
		return sMovieBackdropFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getTvShowThumbFolder(Context c) {
		if (sTvShowThumbFolder == null) {
			sTvShowThumbFolder = getSubAppFolder(c, "tvshows-thumbs");
			sTvShowThumbFolder.mkdirs();
		}
		return sTvShowThumbFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getTvShowBackdropFolder(Context c) {
		if (sTvShowBackdropFolder == null) {
			sTvShowBackdropFolder = getSubAppFolder(c, "tvshows-backdrops");
			sTvShowBackdropFolder.mkdirs();
		}
		return sTvShowBackdropFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getTvShowEpisodeFolder(Context c) {
		if (sTvShowEpisodeFolder == null) {
			sTvShowEpisodeFolder = getSubAppFolder(c, "tvshows-episodes");
			sTvShowEpisodeFolder.mkdirs();
		}
		return sTvShowEpisodeFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific image.
	 */
	public static File getTvShowSeasonFolder(Context c) {
		if (sTvShowSeasonFolder == null) {
			sTvShowSeasonFolder = getSubAppFolder(c, "tvshows-seasons");
			sTvShowSeasonFolder.mkdirs();
		}
		return sTvShowSeasonFolder;
	}

	/*
	 * Please refrain from using this when you need a File object for a specific video.
	 */
	public static File getAvailableOfflineFolder(Context c) {
		if (sAvailableOfflineFolder == null) {
			sAvailableOfflineFolder = getSubAppFolder(c, "offline_storage");
			sAvailableOfflineFolder.mkdirs();
		}
		return sAvailableOfflineFolder;
	}

	/*
	 * Cache folder is used to store videos that are available offline as well
	 * as user profile photo from Trakt.
	 */
	public static File getCacheFolder(Context c) {
		if (sCacheFolder == null) {
			sCacheFolder = getSubAppFolder(c, "app_cache");
			sCacheFolder.mkdirs();
		}
		return sCacheFolder;
	}

	public static TvShowApiService getTvShowService(Context context) {
		return TMDbTvShowService.getInstance(context);
	}

	public static MovieApiService getMovieService(Context context) {
		return TMDbMovieService.getInstance(context);
	}

	/**
	 * This is used as an optimization to loading the movie library view.
	 * @param filepaths
	 */
	public static void setMovieFilepaths(ArrayListMultimap<String, String> filepaths) {
		mMovieFilepaths = filepaths;
	}

	public static List<String> getMovieFilepaths(String id) {
		if (mMovieFilepaths != null && mMovieFilepaths.containsKey(id))
			return mMovieFilepaths.get(id);
		return null;
	}

	/**
	 * OkHttpClient singleton with 2 MB cache.
	 * @return
	 */
	public static OkHttpClient getOkHttpClient() {
		if (mOkHttpClient == null) {
			mOkHttpClient = new OkHttpClient();

			File cacheDir = getContext().getCacheDir();
			Cache cache = new Cache(cacheDir, 2 * 1024 * 1024);
			mOkHttpClient.setCache(cache);
		}

		return mOkHttpClient;
	}

	public static void clearPicassoCache(Context context) {
		PicassoTools.clearCache(getPicasso(context));
	}
} public class TvShow implements Comparable<TvShow> {

	private Context mContext;
	private String TITLE, DESCRIPTION, RATING, GENRES, ACTORS, CERTIFICATION, FIRST_AIR_DATE, RUNTIME, LATEST_EPISODE_AIR_DATE;
	private String mId, mGetReleaseYear, mTitle;
	private boolean mFavorite;
	private File mThumbnail;

	public TvShow(Context context, String id, String title, String description, String rating, String genres, String actors, String certification, String firstAirdate, String runtime, String isFavorite, String latestEpisodeAirDate) {

		// Set up episode fields based on constructor
		mContext = context;
		mId = id;
		TITLE = title;
		DESCRIPTION = description;
		RATING = rating;
		GENRES = genres;
		ACTORS = actors;
		CERTIFICATION = certification;
		FIRST_AIR_DATE = firstAirdate;
		RUNTIME = runtime;
		LATEST_EPISODE_AIR_DATE = latestEpisodeAirDate;
		mFavorite = !(isFavorite.equals("0") || isFavorite.isEmpty());

		// Thumbnail
		mThumbnail = FileUtils.getTvShowThumb(mContext, mId);

		// Title		
		if (TextUtils.isEmpty(TITLE)) {
			mTitle = "";
		} else {
			mTitle = TITLE;
		}

		// getReleaseYear()
		if (!TextUtils.isEmpty(FIRST_AIR_DATE)) {
			String YEAR = FIRST_AIR_DATE.trim();
			try {
				if (YEAR.substring(4,5).equals("-") && YEAR.substring(7,8).equals("-")) {
					mGetReleaseYear = YEAR.substring(0,4);
				} else {
					mGetReleaseYear = mContext.getString(R.string.unknownYear);
				}
			} catch (Exception e) {
				if (YEAR.length() == 4)
					mGetReleaseYear = YEAR;
				else
					mGetReleaseYear = mContext.getString(R.string.unknownYear);
			}
		} else {
			mGetReleaseYear = mContext.getString(R.string.unknownYear);
		}
	}

	public String getTitle() {
		return mTitle;
	}

	/**
	 * This is only used for the SectionIndexer in the overview
	 */
	@Override
	public String toString() {
		try {
			return getTitle().substring(0, 1);
		} catch (Exception e) {
			return "";
		}
	}

	/**
	 * Get the TV show ID
	 * @return
	 */
	public String getId() {
		if (mId == null || mId.isEmpty()) {
			return "NOSHOW";
		} else {
			return mId;
		}
	}
	
	public static final int TMDB = 1;
	
	/**
	 * Get the service provider for the ID.
	 * @return
	 */
	public int getIdType() {
		return TMDB;
	}

	public String getDescription() {
		if (DESCRIPTION == null || DESCRIPTION.isEmpty()) {
			return mContext.getString(R.string.stringNoPlot);
		} else {
			return DESCRIPTION;
		}
	}

	public File getCoverPhoto() {
		return getThumbnail();
	}

	public File getThumbnail() {
		return mThumbnail;
	}

	public String getBackdrop() {
		return FileUtils.getTvShowBackdrop(mContext, mId).getAbsolutePath();
	}
	
	public void setRating(int rating) {
		RATING = String.valueOf(Double.valueOf((double) rating / 10));
	}

	public String getRating() {
		return RATING;
	}

	public double getRawRating() {
		try {
			return Double.valueOf(RATING);
		} catch (NumberFormatException e) {
			return 0.0;
		}
	}

	public double getWeightedRating() {
		if (isFavorite())
			return (10 + getRawRating()) / 2;
		return (getRawRating() + 5) / 2;
	}

	public String getGenres() {
		return StringUtils.replacePipesWithCommas(GENRES);
	}

	public String getActors() {
		return StringUtils.replacePipesWithCommas(ACTORS);
	}
	
	public void setCertification(String certification) {
		CERTIFICATION = certification;
	}

	public String getCertification() {
		return CERTIFICATION;
	}

	public void setFirstAiredDate(int year, int month, int day) {
		FIRST_AIR_DATE = year + "-" + MizLib.addIndexZero(month) + "-" + MizLib.addIndexZero(day);
	}
	
	public String getFirstAirdate() {
		return FIRST_AIR_DATE;
	}
	
	public String getLatestEpisodeAirdate() {
		return LATEST_EPISODE_AIR_DATE;
	}

	public String getFirstAirdateYear() {
		try {
			return FIRST_AIR_DATE.substring(0, 4);
		} catch (Exception e) {
			return "N/A";
		}
	}

	public void setRuntime(int runtime) {
		RUNTIME = String.valueOf(runtime);
	}
	
	public String getRuntime() {
		if (!MizLib.isNumber(RUNTIME) || TextUtils.isEmpty(RUNTIME))
			return "0";
		return RUNTIME;
	}

	public boolean isFavorite() {
		return mFavorite;
	}

	public void setFavorite(boolean fav) {
		mFavorite = fav;
	}

	public String getFavorite() {
		return mFavorite ? "1" : "0";
	}

	@Override
	public int compareTo(TvShow another) {
		return getTitle().compareToIgnoreCase(another.getTitle());
	}

	public String getReleaseYear() {
		return mGetReleaseYear;
	}

    public boolean hasOfflineCopy(Filepath path) {
        return getOfflineCopyFile(path).exists();
    }

    public File getOfflineCopyFile(Filepath path) {
        return FileUtils.getOfflineFile(mContext, path.getFilepath());
    }
} public class FileUtils {

	private FileUtils() {} // No instantiation!
	
	public static File getDatabaseFile(Context context) {
		return context.getDatabasePath(DatabaseHelper.DATABASE_NAME);
	}
	
	public static void copyFile(File src, File dst) throws IOException {
		InputStream in = new FileInputStream(src);
		OutputStream out = new FileOutputStream(dst);

		// Transfer bytes from in to out
		byte[] buf = new byte[1024];
		int len;
		while ((len = in.read(buf)) > 0) {
			out.write(buf, 0, len);
		}
		in.close();
		out.close();
	}

	public static void deleteRecursive(File fileOrDirectory, boolean deleteTopFolder) {
		if (fileOrDirectory.isDirectory()) {
			File[] listFiles = fileOrDirectory.listFiles();
			if (listFiles != null) {
				int count = listFiles.length;
				for (int i = 0; i < count; i++)
					deleteRecursive(listFiles[i], true);
			}
		}

		if (deleteTopFolder)
			fileOrDirectory.delete();
	}

	public static File getMovieThumb(Context c, String movieId) {
		return new File(MizuuApplication.getMovieThumbFolder(c), movieId + ".jpg");
	}

	public static File getMovieBackdrop(Context c, String movieId) {
		return new File(MizuuApplication.getMovieBackdropFolder(c), movieId + "_bg.jpg");
	}

	public static File getTvShowThumb(Context c, String showId) {
		return new File(MizuuApplication.getTvShowThumbFolder(c), showId + ".jpg");
	}

	public static File getTvShowBackdrop(Context c, String showId) {
		return new File(MizuuApplication.getTvShowBackdropFolder(c), showId + "_tvbg.jpg");
	}

	public static File getTvShowEpisode(Context c, String showId, String season, String episode) {
		return new File(MizuuApplication.getTvShowEpisodeFolder(c), showId + "_S" + MizLib.addIndexZero(season) + "E" + MizLib.addIndexZero(episode) + ".jpg");
	}

	public static File getTvShowEpisode(Context c, String showId, int season, int episode) {
		return getTvShowEpisode(c, showId, String.valueOf(season), String.valueOf(episode));
	}

	public static File getTvShowSeason(Context c, String showId, String season) {
		return new File(MizuuApplication.getTvShowSeasonFolder(c), showId + "_S" + MizLib.addIndexZero(season) + ".jpg");
	}

	public static File getTvShowSeason(Context c, String showId, int season) {
		return getTvShowSeason(c, showId, String.valueOf(season));
	}

	public static File getOfflineFile(Context c, String filepath) {
		return new File(MizuuApplication.getAvailableOfflineFolder(c), MizLib.md5(filepath) + "." + StringUtils.getExtension(filepath));
	}

    public static boolean hasOfflineCopy(Context c, Filepath path) {
        return getOfflineCopyFile(c, path).exists();
    }

    private static File getOfflineCopyFile(Context c, Filepath path) {
        return getOfflineFile(c, path.getFilepath());
    }

    public static String copyDatabase(Context context) {
        try {
            File newPath = new File(MizuuApplication.getAppFolder(context), "database.db");
            newPath.createNewFile();
            newPath.setReadable(true);
            FileUtils.copyFile(FileUtils.getDatabaseFile(context), newPath);

            return newPath.exists() ? newPath.getAbsolutePath() : null;
        } catch (IOException e) {
            return null;
        }
    }
} public class LocalBroadcastUtils {

	public static final String UPDATE_MOVIE_LIBRARY = "mizuu-movies-update";
	public static final String UPDATE_TV_SHOW_LIBRARY = "mizuu-tvshows-update";
	public static final String CLEAR_IMAGE_CACHE = "clear-image-cache";
    public static final String UPDATE_TV_SHOW_SEASONS_OVERVIEW = "mizuu-tvshows-seasons-update";
    public static final String UPDATE_TV_SHOW_EPISODES_OVERVIEW = "mizuu-tvshows-episodes-update";
    public static final String UPDATE_TV_SHOW_EPISODE_DETAILS_OVERVIEW = "mizuu-tvshows-episode-details-update";
	
	private LocalBroadcastUtils() {} // No instantiation
	
	/**
	 * Force the movie library to clear the cache and reload everything.
	 * @param context
	 */
	public static void updateMovieLibrary(Context context) {
		LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(UPDATE_MOVIE_LIBRARY));
	}
	
	/**
	 * Force the TV show library to clear the cache and reload everything.
	 * @param context
	 */
	public static void updateTvShowLibrary(Context context) {
		LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(UPDATE_TV_SHOW_LIBRARY));
	}
	
	/**
	 * Clear the image cache.
	 * @param context
	 */
	public static void clearImageCache(Context context) {
		MizuuApplication.clearPicassoCache(context);
		LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(CLEAR_IMAGE_CACHE));
	}

    /**
     * Force the TV show seasons overview to reload.
     * @param context
     */
    public static void updateTvShowSeasonsOverview(Context context) {
        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(UPDATE_TV_SHOW_SEASONS_OVERVIEW));
    }

    /**
     * Force the TV show episodes overview to reload.
     * @param context
     */
    public static void updateTvShowEpisodesOverview(Context context) {
        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(UPDATE_TV_SHOW_EPISODES_OVERVIEW));
    }

    /**
     * Force the TV show details view to reload.
     * @param context
     */
    public static void updateTvShowEpisodeDetailsView(Context context) {
        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(UPDATE_TV_SHOW_EPISODE_DETAILS_OVERVIEW));
    }
} public class VideoUtils {

	// TODO Move all related methods from MizLib, etc. into this class

	private VideoUtils() {} // No instantiation

	public static boolean playVideo(Activity activity, String filepath, int filetype, Object videoObject) {
		boolean playbackStarted = true;

		if (filetype == FileSource.SMB) {
			playbackStarted = playNetworkFile(activity, filepath, videoObject);
		} else {
			try { // Attempt to launch intent with the video MIME wildcard
				activity.startActivity(getVideoIntent(filepath, videoObject));
			} catch (Exception e) {
				playbackStarted = false;
				Toast.makeText(activity, activity.getString(R.string.noVideoPlayerFound), Toast.LENGTH_LONG).show();
			}
		}

		return playbackStarted;
	}

	private static boolean playNetworkFile(final Activity activity, final String filepath, final Object videoObject) {
		if (!MizLib.isWifiConnected(activity)) {
			Toast.makeText(activity, activity.getString(R.string.noConnection), Toast.LENGTH_LONG).show();
			return false;
		}

		int bufferSize;
		String buff = PreferenceManager.getDefaultSharedPreferences(activity).getString(BUFFER_SIZE, activity.getString(R.string._16kb));
		if (buff.equals(activity.getString(R.string._16kb)))
			bufferSize = 8192 * 2; // This appears to be the limit for most video players
		else bufferSize = 8192;

		final Streamer s = Streamer.getInstance();
		if (s != null)
			s.setBufferSize(bufferSize);
		else {
			Toast.makeText(activity, activity.getString(R.string.errorOccured), Toast.LENGTH_SHORT).show();
			return false;
		}

		int contentType = (videoObject instanceof Movie) ? MizLib.TYPE_MOVIE : MizLib.TYPE_SHOWS;
		final SmbLogin auth = MizLib.getLoginFromFilepath(contentType, filepath);

		new Thread(){
			public void run(){
				try{
					final SmbFile file = new SmbFile(
							MizLib.createSmbLoginString(
									auth.getDomain(),
									auth.getUsername(),
									auth.getPassword(),
									filepath,
									false
							));

					s.setStreamSrc(file, MizLib.getSubtitleFiles(filepath, auth)); //the second argument can be a list of subtitle files
					activity.runOnUiThread(new Runnable(){
						public void run(){
							try{
								Uri uri = Uri.parse(s.getUrl() + Uri.fromFile(new File(Uri.parse(filepath).getPath())).getEncodedPath());
								activity.startActivity(getVideoIntent(uri, videoObject));
							} catch (Exception e) {
								try { // Attempt to launch intent based on wildcard MIME type
									Uri uri = Uri.parse(s.getUrl() + Uri.fromFile(new File(Uri.parse(filepath).getPath())).getEncodedPath());
									activity.startActivity(getVideoIntent(uri, "video/*", videoObject));
								} catch (Exception e2) {
									Toast.makeText(activity, activity.getString(R.string.noVideoPlayerFound), Toast.LENGTH_LONG).show();
								}
							}
						}
					});
				}
				catch (MalformedURLException e) {}
				catch (UnsupportedEncodingException e1) {}
			}
		}.start();

		return true;
	}

	public static String startSmbServer(final Activity activity, final String filepath, final Object videoObject) {
		if (!MizLib.isWifiConnected(activity)) {
			Toast.makeText(activity, activity.getString(R.string.noConnection), Toast.LENGTH_LONG).show();
			return "";
		}

		int bufferSize;
		String buff = PreferenceManager.getDefaultSharedPreferences(activity).getString(BUFFER_SIZE, activity.getString(R.string._16kb));
		if (buff.equals(activity.getString(R.string._16kb)))
			bufferSize = 8192 * 2; // This appears to be the limit for most video players
		else bufferSize = 8192;

		final Streamer s = Streamer.getInstance();
		if (s != null)
			s.setBufferSize(bufferSize);
		else {
			Toast.makeText(activity, activity.getString(R.string.errorOccured), Toast.LENGTH_SHORT).show();
			return "";
		}

		int contentType = (videoObject instanceof Movie) ? MizLib.TYPE_MOVIE : MizLib.TYPE_SHOWS;
		final SmbLogin auth = MizLib.getLoginFromFilepath(contentType, filepath);

		new Thread(){
			public void run(){
				try{
					final SmbFile file = new SmbFile(
							MizLib.createSmbLoginString(
									auth.getDomain(),
									auth.getUsername(),
									auth.getPassword(),
									filepath,
									false
							));

					s.setStreamSrc(file, MizLib.getSubtitleFiles(filepath, auth)); //the second argument can be a list of subtitle files
				}
				catch (MalformedURLException e) {}
				catch (UnsupportedEncodingException e1) {}
			}
		}.start();

		return Uri.parse(s.getUrl() + Uri.fromFile(new File(Uri.parse(filepath).getPath())).getEncodedPath()).toString();
	}

	public static void playTrailer(final Activity activity, final Movie movie) {
		String localTrailer = "";
		for (Filepath path : movie.getFilepaths()) {
			if (path.getType() == FileSource.FILE) {
				localTrailer = path.getFullFilepath();
				break;
			}
		}

		localTrailer = movie.getLocalTrailer(localTrailer);

		if (!TextUtils.isEmpty(localTrailer)) {
			try { // Attempt to launch intent based on the MIME type
				activity.startActivity(getVideoIntent(localTrailer, movie.getTitle() + " " + activity.getString(R.string.detailsTrailer)));
			} catch (Exception e) {
				Toast.makeText(activity, activity.getString(R.string.noVideoPlayerFound), Toast.LENGTH_LONG).show();
			}
		} else {
			if (!TextUtils.isEmpty(movie.getTrailer())) {
				Intent intent = new Intent(Intent.ACTION_VIEW);
				intent.setData(Uri.parse(movie.getTrailer()));
				activity.startActivity(intent);
			} else {
				new TmdbTrailerSearch(activity, movie.getTmdbId()).execute();
			}
		}
	}

	public static Intent getVideoIntent(String fileUrl, Object videoObject) {
		if (fileUrl.startsWith("http"))
			return getVideoIntent(Uri.parse(fileUrl), videoObject);

		Intent videoIntent = new Intent(Intent.ACTION_VIEW);
		videoIntent.setDataAndType(Uri.fromFile(new File(fileUrl)), "video/*");
		videoIntent.putExtras(getVideoIntentBundle(videoObject));

		return videoIntent;
	}

	public static Intent getVideoIntent(Uri file, Object videoObject) {
		Intent videoIntent = new Intent(Intent.ACTION_VIEW);
		videoIntent.setDataAndType(file, "video/*");
		videoIntent.putExtras(getVideoIntentBundle(videoObject));

		return videoIntent;
	}

	public static Intent getVideoIntent(String fileUrl, String mimeType, Object videoObject) {
		if (fileUrl.startsWith("http"))
			return getVideoIntent(Uri.parse(fileUrl), mimeType, videoObject);

		Intent videoIntent = new Intent(Intent.ACTION_VIEW);
		videoIntent.setDataAndType(Uri.fromFile(new File(fileUrl)), mimeType);
		videoIntent.putExtras(getVideoIntentBundle(videoObject));

		return videoIntent;
	}

	public static Intent getVideoIntent(Uri file, String mimeType, Object videoObject) {
		Intent videoIntent = new Intent(Intent.ACTION_VIEW);
		videoIntent.setDataAndType(file, mimeType);
		videoIntent.putExtras(getVideoIntentBundle(videoObject));

		return videoIntent;
	}

	private static Bundle getVideoIntentBundle(Object videoObject) {
		Bundle b = new Bundle();
		String title = "";
		if (videoObject instanceof Movie) {
			title = ((Movie) videoObject).getTitle();
			b.putString("plot", ((Movie) videoObject).getPlot());
			b.putString("date", ((Movie) videoObject).getReleasedate());
			b.putDouble("rating", ((Movie) videoObject).getRawRating());
			b.putString("cover", ((Movie) videoObject).getThumbnail().getAbsolutePath());
			b.putString("genres", ((Movie) videoObject).getGenres());
		} else if (videoObject instanceof TvShowEpisode) {
			title = ((TvShowEpisode) videoObject).getTitle();
			b.putString("plot", ((TvShowEpisode) videoObject).getDescription());
			b.putString("date", ((TvShowEpisode) videoObject).getReleasedate());
			b.putDouble("rating", ((TvShowEpisode) videoObject).getRawRating());
			b.putString("cover", ((TvShowEpisode) videoObject).getEpisodePhoto().getAbsolutePath());
			b.putString("episode", ((TvShowEpisode) videoObject).getEpisode());
			b.putString("season", ((TvShowEpisode) videoObject).getSeason());
		} else {
			title = (String) videoObject;
		}
		b.putString("title", title);
		b.putString("forcename", title);
		b.putBoolean("forcedirect", true);
		return b;
	}
} public class ViewUtils {

    private ViewUtils() {} // No instantiation

    /**
     * Returns a actor card with name, character, image and click listener.
     * @param context
     * @param picasso
     * @param actor
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupActorCard(final Activity context, Picasso picasso, final Actor actor) {
        View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small, null);

        // Load image
        picasso.load(actor.getUrl()).placeholder(R.color.card_background_dark).error(R.drawable.noactor).config(MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set title
        ((TextView) v.findViewById(R.id.text)).setText(actor.getName());
        ((TextView) v.findViewById(R.id.text)).setTypeface(TypefaceUtils.getRobotoMedium(context));

        // Set subtitle
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setText(actor.getCharacter());
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setSingleLine(true);

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(IntentUtils.getActorIntent(context, actor));
            }
        });

        return v;
    }

    /**
     * Returns a movie card with title, release date, image and click listener.
     * @param context
     * @param picasso
     * @param movie
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupMovieCard(final Activity context, Picasso picasso, final WebMovie movie) {
        final View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small, null);

        // Load image
        picasso.load(movie.getUrl()).placeholder(R.color.card_background_dark).error(R.drawable.loading_image).config(MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set title
        ((TextView) v.findViewById(R.id.text)).setText(movie.getTitle());
        ((TextView) v.findViewById(R.id.text)).setTypeface(TypefaceUtils.getRobotoMedium(context));

        // Set subtitle
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setText(movie.getSubtitle());
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setSingleLine(true);

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(context, v.findViewById(R.id.cover), "cover");
                ActivityCompat.startActivity(context, IntentUtils.getTmdbMovieDetails(context, movie), options.toBundle());
            }
        });

        return v;
    }

    /**
     * Returns a TV show card with title, release date, image and click listener.
     * @param context
     * @param picasso
     * @param show
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupTvShowCard(final Context context, Picasso picasso, final WebMovie show) {
        View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small, null);

        // Load image
        picasso.load(show.getUrl()).placeholder(R.color.card_background_dark).error(R.drawable.loading_image).config(MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set title
        ((TextView) v.findViewById(R.id.text)).setText(show.getTitle());
        ((TextView) v.findViewById(R.id.text)).setTypeface(TypefaceUtils.getRobotoMedium(context));

        // Set subtitle
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setText(show.getSubtitle());
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setSingleLine(true);

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(IntentUtils.getTmdbTvShowLink(context, show));
            }
        });

        return v;
    }

    /**
     * Returns a TV show season card with title, release date, image and click listener.
     * @param context
     * @param picasso
     * @param season
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupTvShowSeasonCard(final Activity context, Picasso picasso, final GridSeason season, final int toolbarColor) {
        final View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small, null);

        // Load image
        picasso.load(season.getCover()).placeholder(R.color.card_background_dark).error(R.drawable.loading_image).config(
            MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set title
        ((TextView) v.findViewById(R.id.text)).setText(season.getHeaderText());
        ((TextView) v.findViewById(R.id.text)).setTypeface(TypefaceUtils.getRobotoMedium(context));

        // Set subtitle
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setText(season.getSimpleSubtitleText());
        ((TextView) v.findViewById(R.id.gridCoverSubtitle)).setSingleLine(true);

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivityForResult(IntentUtils.getTvShowSeasonIntent(context, season.getShowId(), season.getSeason(), season.getEpisodeCount(), toolbarColor), 0);
            }
        });

        return v;
    }

    /**
     * Returns a photo card with image and click listener.
     * @param context
     * @param picasso
     * @param url
     * @param items
     * @param index
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupPhotoCard(final Context context, Picasso picasso, final String url, final List<String> items, final int index) {
        final View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small_no_text, null);

        // Load image
        picasso.load(url).placeholder(R.color.card_background_dark).error(R.drawable.noactor).config(MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(IntentUtils.getActorPhotoIntent(context, items, index));
            }
        });

        return v;
    }

    /**
     * Returns a photo card with image and click listener.
     * @param context
     * @param picasso
     * @param url
     * @param items
     * @param index
     * @return
     */
    @SuppressLint("InflateParams")
    public static View setupTaggedPhotoCard(final Context context, Picasso picasso, final String url, final List<String> items, final int index) {
        View v = LayoutInflater.from(context).inflate(R.layout.horizontal_grid_item_small_landscape_no_text, null);

        // Load image
        picasso.load(url).placeholder(R.color.card_background_dark).error(R.drawable.noactor).config(MizuuApplication.getBitmapConfig()).into(((ImageView) v.findViewById(R.id.cover)));

        // Set click listener
        v.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(IntentUtils.getActorTaggedPhotoIntent(context, items, index));
            }
        });

        return v;
    }

    /**
     * Animates the transition when changing the maxLines
     * attribute of a TextView.
     * @param text
     * @param maxLines
     */
    public static void animateTextViewMaxLines(TextView text, int maxLines) {
        try {
            ObjectAnimator animation = ObjectAnimator.ofInt(text, "maxLines", maxLines);
            animation.setInterpolator(new AccelerateInterpolator());
            animation.setDuration(200);
            animation.start();
        } catch (Exception e) {
            // Some devices crash at runtime when using the ObjectAnimator
            text.setMaxLines(maxLines);
        }
    }

    public static void animateFabJump(View fab, Animator.AnimatorListener listener) {
        try {
            ObjectAnimator animation = ObjectAnimator.ofFloat(fab, "translationY", -10f, -5f, 0f, 5f, 10f, 5f, 0f, -5f, -10f, -5f, 0f);
            animation.setDuration(350);
            animation.addListener(listener);
            animation.start();
        } catch (Exception e) {
            // Some devices crash at runtime when using the ObjectAnimator
        }
    }

    private static int defaultTitleTextColor = -1;

    /**
     * Update the Toolbar background color and title.
     * @param activity
     * @param toolbar
     * @param alpha
     * @param title
     * @param color
     */
    public static void updateToolbarBackground(Activity activity, Toolbar toolbar,
                                               int alpha, String title, int color) {
        if (defaultTitleTextColor == -1) {
            int[] textColorAttr = new int[]{R.attr.actionMenuTextColor};
            TypedValue typedValue = new TypedValue();
            int indexOfAttrTextColor = 0;
            TypedArray a = activity.obtainStyledAttributes(typedValue.data, textColorAttr);
            defaultTitleTextColor = a.getColor(indexOfAttrTextColor, -1);
            a.recycle();
        }
        toolbar.setTitle(title);
        toolbar.setTitleTextColor(adjustAlpha(defaultTitleTextColor, alpha));
        int toolbarColor = adjustAlpha(color, alpha);
        if (MizLib.hasJellyBean()) {
            int topColor = darkenColor(color, alpha / 255f);
            topColor = adjustAlpha(topColor, Math.max(125, alpha));
            int[] colors = {topColor, toolbarColor};
            toolbar.setBackground(new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors));
        } else {
            toolbar.setBackgroundColor(toolbarColor);
        }
    }

    public static int adjustAlpha(int color, int alpha) {
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        return Color.argb(alpha, red, green, blue);
    }

    public static int darkenColor(int color, float factor) {
        final float[] hsv = new float[3];
        Color.colorToHSV(color, hsv);
        hsv[2] *= factor;
        return Color.HSVToColor(hsv);
    }

    public static boolean isTranslucentDecorAvailable(Context context) {
        int id = context.getResources().getIdentifier("config_enableTranslucentDecor", "bool", "android");
        return (id == 0) && context.getResources().getBoolean(id);
    }

    public static int getStatusBarHeight(Context context) {
        int result = 0;
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = context.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    public static int getNavigationDrawerWidth(Context context) {
        int drawerWidth;

        // The navigation drawer should have a width equal to
        // the screen width minus the Toolbar height - at least
        // on mobile devices. Tablets are accounted for below.
        int toolbarHeight = MizLib.getActionBarHeight(context);

        // Get the display size
        Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
                .getDefaultDisplay();

        Point size = new Point();
        display.getSize(size);

        // Get the smallest number
        int smallestDisplayWidth = Math.min(size.x, size.y);

        // Calculate the drawer width
        drawerWidth = smallestDisplayWidth - toolbarHeight;

        // Make sure that the calculated drawer width
        // isn't greater than the max width, i.e.
        // 5 times the standard increment (56dp for
        // mobile or 64dp for tablets).
        int maxWidth = MizLib.convertDpToPixels(context, 5 * (MizLib.isTablet(context) ? 64 : 56));

        if (drawerWidth > maxWidth)
            drawerWidth = maxWidth;

        return drawerWidth;
    }

    /**
     * Since Toolbar needs to be bigger than the default height in some cases,
     * this method will change the height to include the status bar on Kitkat and newer.
     * @param context
     * @param toolbar
     */
    public static void setProperToolbarSize(Context context, Toolbar toolbar) {
        toolbar.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                MizLib.hasKitKat() ? MizLib.getActionBarAndStatusBarHeight(context) : MizLib.getActionBarHeight(context)));
    }

    public static void setupWindowFlagsForStatusbarOverlay(Window window, boolean setBackgroundResource) {

        if (MizLib.isKitKat()) {
            // If we're running on KitKat, we want to enable
            // the translucent status bar
            window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }

        if (MizLib.hasKitKat()) {
            // If we're running on KitKat or above, we want to show
            // the background image beneath the status bar as well.
            window.getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        }

        // Make the status bar color transparent to begin with
        if (MizLib.hasLollipop())
            window.setStatusBarColor(Color.TRANSPARENT);

        // If requested, set a background resource on the Window object
        if (setBackgroundResource)
            window.setBackgroundDrawableResource(R.drawable.bg);
    }

    public static void setLayoutParamsForDetailsEmptyView(Context context, View layout, ImageView background, ObservableScrollView scrollView, ViewTreeObserver.OnGlobalLayoutListener listener) {
        if (!MizLib.isPortrait(context)) {
            // Let's set the size of the empty view on the scroll container
            View empty = layout.findViewById(R.id.empty_view);

            if (empty == null)
                return;

            // First, we get the height of the background image, since that
            // fills the available screen estate in its entirety
            int fullHeight = background.getHeight();

            // Then we get the content height - this is how much of the content
            // will be shown on the screen at a minimum
            int contentHeight = context.getResources().getDimensionPixelSize(R.dimen.content_details_main_height);

            // Finally we set the empty view to fill the width and have a height
            // that fills the gap between the full height and content height
            empty.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, fullHeight - contentHeight));
        }

        // Remove the ViewTreeObserver when we're done :-)
        MizLib.removeViewTreeObserver(scrollView.getViewTreeObserver(), listener);
    }

    public static void handleOnScrollChangedEvent(Activity activity, View layout, View background, String title, int height, int t, Toolbar toolbar, int toolbarColor) {
        final int headerHeight = (MizLib.isPortrait(activity) ? background.getHeight() : layout.findViewById(R.id.empty_view).getHeight()) - height;
        final float ratio = (float) Math.min(Math.max(t, 0), headerHeight) / headerHeight;
        final int newAlpha = (int) (ratio * 255);

        // Update the Toolbar
        ViewUtils.updateToolbarBackground(activity, toolbar, newAlpha, title, toolbarColor);

        if (MizLib.isPortrait(activity)) {
            // Such parallax, much wow
            background.setPadding(0, (int) (t / 1.5), 0, 0);
        }

        // Update scroll background in landscape mode
        if (!MizLib.isPortrait(activity)) {
            View v = layout.findViewById(R.id.background_view);
            View v2 = layout.findViewById(R.id.background_view_more);
            final int backgroundAlpha = (int) (ratio * 15) + 240;

            v.setBackgroundColor(ViewUtils.adjustAlpha(Color.parseColor("#303030"), backgroundAlpha));
            v2.setBackgroundColor(ViewUtils.adjustAlpha(Color.parseColor("#303030"), backgroundAlpha));
        }
    }

    public static void setToolbarAndStatusBarColor(android.support.v7.app.ActionBar actionBar, Window window, int color) {
        if (color != 0) {
            actionBar.setBackgroundDrawable(new ColorDrawable(color));
            if (MizLib.hasLollipop())
                window.setStatusBarColor(color);
        }
    }

    public static int getGridViewThumbSize(Context context) {
        int thumbSize = 0;

        String thumbnailSize = PreferenceManager.getDefaultSharedPreferences(context).getString(GRID_ITEM_SIZE, context.getString(R.string.normal));
        if (thumbnailSize.equals(context.getString(R.string.large)))
            thumbSize = (int) (context.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size) * 1.33);
        else if (thumbnailSize.equals(context.getString(R.string.normal)))
            thumbSize = (context.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size) * 1);
        else
            thumbSize = (int) (context.getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size) * 0.75);

        return thumbSize;
    }
}
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton;
import com.miz.apis.tmdb.TMDbTvShowService;
import com.miz.apis.trakt.Trakt;
import com.miz.base.MizActivity;
import com.miz.db.DbAdapterTvShowEpisodeMappings;
import com.miz.db.DbAdapterTvShowEpisodes;
import com.miz.db.DbAdapterTvShows;
import com.miz.functions.Actor;
import com.miz.functions.EpisodeCounter;
import com.miz.functions.FileSource;
import com.miz.functions.Filepath;
import com.miz.functions.GridSeason;
import com.miz.functions.IntentKeys;
import com.miz.functions.MizLib;
import com.miz.functions.PaletteLoader;
import com.miz.functions.SimpleAnimatorListener;
import com.miz.mizuu.EditTvShow;
import com.miz.mizuu.IdentifyTvShow;
import com.miz.mizuu.MizuuApplication;
import com.miz.mizuu.R;
import com.miz.mizuu.ShowCoverFanartBrowser;
import com.miz.mizuu.TvShow;
import com.miz.mizuu.TvShowEpisode;
import com.miz.utils.FileUtils;
import com.miz.utils.IntentUtils;
import com.miz.utils.LocalBroadcastUtils;
import com.miz.utils.TypefaceUtils;
import com.miz.utils.VideoUtils;
import com.miz.utils.ViewUtils;
import com.miz.views.HorizontalCardLayout;
import com.miz.views.ObservableScrollView;
import com.miz.views.ObservableScrollView.OnScrollChangedListener;
import com.squareup.otto.Bus;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/*
 * Copyright (C) 2014 Michell Bak
 *
 * 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 com.miz.mizuu.fragments;




public class TvShowDetailsFragment extends Fragment {

    private Activity mContext;
    private DbAdapterTvShows dbHelper;
    private TvShow thisShow;
    private TextView textTitle, textPlot, textGenre, textRuntime, textReleaseDate, textRating, textCertification;
    private ImageView background, cover;
    private ObservableScrollView mScrollView;
    private View mDetailsArea;
    private Picasso mPicasso;
    private Typeface mMediumItalic, mMedium, mBold, mCondensedRegular;
    private Bus mBus;
    private HorizontalCardLayout mSeasonsLayout, mActorsLayout;
    private int mImageThumbSize, mImageThumbSpacing, mToolbarColor = 0;
    private Toolbar mToolbar;
    private FloatingActionButton mFab;
    private PaletteLoader mPaletteLoader;

    /**
     * Empty constructor as per the Fragment documentation
     */
    public TvShowDetailsFragment() {}

    public static TvShowDetailsFragment newInstance(String showId) {
        TvShowDetailsFragment pageFragment = new TvShowDetailsFragment();
        Bundle b = new Bundle();
        b.putString("showId", showId);
        pageFragment.setArguments(b);
        return pageFragment;
    }

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

        setHasOptionsMenu(true);
        setRetainInstance(true);

        mContext = getActivity();

        mBus = MizuuApplication.getBus();