public class LoopApplication extends Application {

    static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    // region Static Variables
    private static LoopApplication currentApplication = null;
    // endregion

    // region Member Variables
    private RefWatcher refWatcher;
    // endregion

    // region Lifecycle Methods
    @Override
    public void onCreate() {
        super.onCreate();

        currentApplication = this;

//        initializeFabric();
        initializeLeakCanary();
        initializeTimber();
        initializeFlurry();
        initializeRealm();
    }
    // endregion

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    // region Helper Methods
    public static LoopApplication getInstance() {
        return currentApplication;
    }

    public static File getCacheDirectory()  {
        return currentApplication.getCacheDir();
    }

    public static RefWatcher getRefWatcher(Context context) {
        LoopApplication application = (LoopApplication) context.getApplicationContext();
        return application.refWatcher;
    }

//    private void initializeFabric(){
//        if (!Fabric.isInitialized()) {
//            final Fabric fabric = new Fabric.Builder(this)
//                    .kits(new Crashlytics())
//                    .debuggable(true)
//                    .build();
//
//            Fabric.with(fabric);
//        }
//    }

    private void initializeLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        refWatcher = LeakCanary.install(this);
    }

    private void initializeTimber(){
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree() {
                // Add the line number to the tag
                @Override
                protected String createStackElementTag(StackTraceElement element) {
                    return String.format("[Line - %s] [Method - %s] [Class - %s]",
                            element.getLineNumber(),
                            element.getMethodName(),
                            super.createStackElementTag(element));
                }
            });
        } else {
            Timber.plant(new ReleaseTree());
        }
    }

    private void initializeFlurry(){
        FlurryAgent.setLogEnabled(false);

        FlurryAgent.init(this, getString(R.string.flurry_api_key));
    }

    private void initializeRealm(){
        Realm.init(this);
        RealmConfiguration config =
                new RealmConfiguration.Builder()
                        .deleteRealmIfMigrationNeeded()
                        .build();
        Realm.setDefaultConfiguration(config);
    }

    // endregion

    // region Inner Classes

    /** A tree which logs important information for crash reporting. */
    private static class ReleaseTree extends Timber.Tree {

        private static final int MAX_LOG_LENGTH = 4000;
        @Override
        protected boolean isLoggable(int priority) {
            if(priority == Log.VERBOSE || priority == Log.DEBUG){
                return false;
            }

            // Only log WARN, INFO, ERROR, WTF
            return true;
        }

        @Override
        protected void log(int priority, String tag, String message, Throwable t) {
            if(isLoggable(priority)){
                //            FakeCrashLibrary.log(priority, tag, message);
//
//            if (t != null) {
//                if (priority == Log.ERROR) {
//                    FakeCrashLibrary.logError(t);
//                } else if (priority == Log.WARN) {
//                    FakeCrashLibrary.logWarning(t);
//                }
//            }

//                Crashlytics.log(priority, tag, message);
//
//                if (t != null) {
//                    if (priority == Log.ERROR) {
//                        Crashlytics.logException(t);
//                    } else if (priority == Log.INFO) {
//                        Crashlytics.log(message);
//                    }
//                }

                // Message is short enough, does not need to be broken into chunks
                if(message.length() < MAX_LOG_LENGTH){
                    if(priority == Log.ASSERT) {
                        Log.wtf(tag, message);
                    } else {
                        Log.println(priority, tag, message);
                    }
                    return;
                }

                // Split by line, then ensure each line can fit into Log's maximum length
                for(int i=0, length = message.length(); i<length; i++) {
                    int newline = message.indexOf('\n', i);
                    newline = newline != -1 ? newline : length;
                    do {
                        int end = Math.min(newline, i + MAX_LOG_LENGTH);
                        String part = message.substring(i, end);
                        if(priority == Log.ASSERT) {
                            Log.wtf(tag, part);
                        } else {
                            Log.println(priority, tag, part);
                        }
                        i = end;
                    } while (i < newline);
                }
            }
        }
    }

    // endregion
} public class AccessToken {

    // region Fields
    private String tokenType;
    private String accessToken;
    // endregion

    // region Constructors
    public AccessToken(String tokenType, String accessToken) {
        this.tokenType = tokenType;
        this.accessToken = accessToken;
    }
    // endregion

    // region Getters
    public String getTokenType() {
        // OAuth requires uppercase Authorization HTTP header value for token type
        if ( ! Character.isUpperCase(tokenType.charAt(0))) {
            tokenType = Character.toString(tokenType.charAt(0)).toUpperCase() + tokenType.substring(1);
        }

        return tokenType;
    }

    public String getAccessToken() {
        return accessToken;
    }
    // endregion
} public class VideoSavedState {

    // region Fields
    private String videoUrl;
    private long currentPosition;
    private float currentVolume;
    private VideoDetailsFragment.PlaybackState playbackState;
    private String castInfo;
    // endregion

    public VideoSavedState(){

    }

    // region Getters
    public String getVideoUrl() {
        return videoUrl;
    }

    public long getCurrentPosition() {
        return currentPosition;
    }

    public float getCurrentVolume() {
        return currentVolume;
    }

    public VideoDetailsFragment.PlaybackState getPlaybackState() {
        return playbackState;
    }

    public String getCastInfo() {
        return castInfo;
    }

    // endregion

    // region Setters
    public void setVideoUrl(String videoUrl) {
        this.videoUrl = videoUrl;
    }

    public void setCurrentPosition(long currentPosition) {
        this.currentPosition = currentPosition;
    }

    public void setCurrentVolume(float currentVolume) {
        this.currentVolume = currentVolume;
    }

    public void setPlaybackState(VideoDetailsFragment.PlaybackState playbackState) {
        this.playbackState = playbackState;
    }

    public void setCastInfo(String castInfo) {
        this.castInfo = castInfo;
    }

    // endregion
} public class ServiceGenerator {

    // region Constants
    private static final int DISK_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
    // endregion

    private static Retrofit.Builder retrofitBuilder
            = new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create());

    private static OkHttpClient defaultOkHttpClient
            = new OkHttpClient.Builder()
                .cache(getCache())
                .build();

    // No need to instantiate this class.
    private ServiceGenerator() {
    }

    public static <S> S createService(Class<S> serviceClass, String baseUrl) {
        return createService(serviceClass, baseUrl, null);
    }

    public static <S> S createService(Class<S> serviceClass, String baseUrl, Interceptor networkInterceptor) {
        OkHttpClient.Builder okHttpClientBuilder = defaultOkHttpClient.newBuilder();

        if(networkInterceptor != null){
            okHttpClientBuilder.addNetworkInterceptor(networkInterceptor);
        }

        OkHttpClient modifiedOkHttpClient = okHttpClientBuilder
                .addInterceptor(getHttpLoggingInterceptor())
                .build();

        retrofitBuilder.client(modifiedOkHttpClient);
        retrofitBuilder.baseUrl(baseUrl);

        Retrofit retrofit = retrofitBuilder.build();
        return retrofit.create(serviceClass);
    }

    private static Cache getCache() {

        Cache cache = null;
        // Install an HTTP cache in the application cache directory.
        try {
            File cacheDir = new File(LoopApplication.getCacheDirectory(), "http");
            cache = new Cache(cacheDir, DISK_CACHE_SIZE);
        } catch (Exception e) {
            Timber.e(e, "Unable to install disk cache.");
        }
        return cache;
    }

    private static HttpLoggingInterceptor getHttpLoggingInterceptor(){
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG) {
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        } else {
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
        }
        return httpLoggingInterceptor;
    }
} public interface VimeoPlayerService {

    String BASE_URL = "http://player.vimeo.com/";

    @GET("video/{videoId}/config")
    Call<VideoConfig> getVideoConfig(@Path("videoId") Long videoId);
} public class AuthorizedNetworkInterceptor implements Interceptor {

    // region Member Variables
    private AccessToken accessToken;
    // endregion

    // region Constructors
    public AuthorizedNetworkInterceptor(AccessToken accessToken) {
        this.accessToken = accessToken;
    }
    // endregion

    @Override
    public Response intercept(Chain chain) throws IOException {
        if (chain != null) {
            Request originalRequest = chain.request();

            if (accessToken != null) {
                Map<String, String> headersMap = new HashMap<>();
                headersMap.put("Authorization", String.format("%s %s", accessToken.getTokenType(), accessToken.getAccessToken()));
                headersMap.put("Accept", "application/vnd.vimeo.*+json; version=3.2");
                Request modifiedRequest = RequestUtility.updateHeaders(originalRequest, headersMap);

                return chain.proceed(modifiedRequest);
            } else {
                return chain.proceed(originalRequest);
            }
        }

        return null;
    }
} public class Video implements Parcelable {

    // region Constants
    public static final String PATTERN = "yyyy-MM-dd'T'hh:mm:ssZ";
    // endregion

    // region Fields
    @SerializedName("uri")
    private String uri;
    @SerializedName("name")
    private String name;
    @SerializedName("description")
    private String description;
    @SerializedName("link")
    private String link;
    @SerializedName("duration")
    private int duration;
    @SerializedName("width")
    private int width;
    @SerializedName("language")
    private String language;
    @SerializedName("height")
    private int height;
    @SerializedName("embed")
    private Embed embed;
    @SerializedName("created_time")
    private String createdTime;
    @SerializedName("modified_time")
    private String modifiedTime;
    @SerializedName("content_rating")
    private List<String> contentRating;
//    @SerializedName("license")
//    private Object license;
    @SerializedName("pictures")
    private Pictures pictures;
    @SerializedName("tags")
    private List<Tag> tags;
    @SerializedName("stats")
    private Stats stats;
    @SerializedName("metadata")
    private Metadata metadata;
    @SerializedName("user")
    private User user;
//    @SerializedName("app")
//    private Object app;
    @SerializedName("status")
    private String status;
//    @SerializedName("embed_presets")
//    private Object embedPresets;
    // endregion

    // region Constructors
    public Video() {
    }

    protected Video(Parcel in) {
        this.uri = in.readString();
        this.name = in.readString();
        this.description = in.readString();
        this.link = in.readString();
        this.duration = in.readInt();
        this.width = in.readInt();
        this.language = in.readString();
        this.height = in.readInt();
        this.embed = in.readParcelable(Embed.class.getClassLoader());
        this.createdTime = in.readString();
        this.modifiedTime = in.readString();
        this.contentRating = in.createStringArrayList();
        this.pictures = in.readParcelable(Pictures.class.getClassLoader());
        this.tags = in.createTypedArrayList(Tag.CREATOR);
        this.stats = in.readParcelable(Stats.class.getClassLoader());
        this.metadata = in.readParcelable(Metadata.class.getClassLoader());
        this.user = in.readParcelable(User.class.getClassLoader());
        this.status = in.readString();
    }
    // endregion

    // region Getters

    public String getUri() {
        return uri;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public String getLink() {
        return link;
    }

    public int getDuration() {
        return duration;
    }

    public int getWidth() {
        return width;
    }

    public String getLanguage() {
        return language;
    }

    public int getHeight() {
        return height;
    }

    public Embed getEmbed() {
        return embed;
    }

    public String getCreatedTime() {
        return createdTime;
    }

    public String getModifiedTime() {
        return modifiedTime;
    }

    public List<String> getContentRating() {
        return contentRating;
    }

    public Pictures getPictures() {
        return pictures;
    }

    public List<Tag> getTags() {
        return tags;
    }

    public Stats getStats() {
        return stats;
    }

    public Metadata getMetadata() {
        return metadata;
    }

    public User getUser() {
        return user;
    }

    public String getStatus() {
        return status;
    }

    public long getId() {
        long id = -1L;
        String uri = getUri();
        if (!TextUtils.isEmpty(uri)) {
            String lastPathSegment = Uri.parse(uri).getLastPathSegment();
            id = Long.parseLong(lastPathSegment);
        }
        return id;
    }

    public String getThumbnailUrl(){
        String thumbnailUrl = "";
        if (pictures != null) {
            List<Size> sizes = pictures.getSizes();
            if (sizes != null && sizes.size() > 0) {
                Size size = sizes.get(sizes.size() - 1);
                if (size != null) {
                    thumbnailUrl = size.getLink();
                }
            }
        }
        return thumbnailUrl;
    }

    public String getFormattedDuration(){
        long minutes = duration / 60;
        long seconds = duration % 60;

        String formattedDuration;
        if (minutes == 0L) {
            if (seconds > 0L) {
                if (seconds < 10L)
                    formattedDuration = String.format("0:0%s", String.valueOf(seconds));
                else
                    formattedDuration = String.format("0:%s", String.valueOf(seconds));
            } else {
                formattedDuration = "0:00";
            }
        } else {
            if (seconds > 0L) {
                if (seconds < 10L)
                    formattedDuration = String.format("%s:0%s", String.valueOf(minutes), String.valueOf(seconds));
                else
                    formattedDuration = String.format("%s:%s", String.valueOf(minutes), String.valueOf(seconds));
            } else {
                formattedDuration = String.format("%s:00", String.valueOf(minutes));
            }
        }
        return formattedDuration;
    }

    public String getCaption(){
        String caption = "";
        int viewCount = 0;
        if (stats != null) {
            viewCount = stats.getPlays();
        }

        String formattedCreatedTime = DateUtility.getFormattedDateAndTime(DateUtility.getCalendar(createdTime, PATTERN), DateUtility.FORMAT_RELATIVE);

        if (viewCount > 0) {
            String formattedViewCount = formatViewCount(viewCount);
            if(!TextUtils.isEmpty(createdTime))
                caption = String.format("%s \u2022 %s", formattedViewCount, formattedCreatedTime);
            else
                caption = formattedViewCount;

        } else {
            caption = formattedCreatedTime;
        }
        return caption;
    }

    private String formatViewCount(int viewCount) {
        String formattedViewCount = "";

        if (viewCount < 1000000000 && viewCount >= 1000000) {
            formattedViewCount = String.format("%dM views", viewCount / 1000000);
        } else if (viewCount < 1000000 && viewCount >= 1000) {
            formattedViewCount = String.format("%dK views", viewCount / 1000);
        } else if (viewCount < 1000 && viewCount > 1) {
            formattedViewCount = String.format("%d views", viewCount);
        } else if (viewCount == 1) {
            formattedViewCount = String.format("%d view", viewCount);
        }

        return formattedViewCount;
    }

    public boolean isLiked(){
        boolean isLiked = false;
        if (metadata != null) {
            Interactions interactions = metadata.getInteractions();
            if (interactions != null) {
                Interaction likeInteraction = interactions.getLike();
                if (likeInteraction != null) {
                    if (likeInteraction.isAdded()) {
                        isLiked = true;
                    }
                }
            }
        }
        return isLiked;
    }

    public boolean isAddedToWatchLater(){
        boolean isAddedToWatchLater = false;
        if (metadata != null) {
            Interactions interactions = metadata.getInteractions();
            if (interactions != null) {
                Interaction watchLaterInteraction = interactions.getWatchlater();
                if (watchLaterInteraction != null) {
                    if (watchLaterInteraction.isAdded()) {
                        isAddedToWatchLater = true;
                    }
                }
            }
        }
        return isAddedToWatchLater;
    }

    public String getFormattedViewCount(){
        String formattedViewCount = "";

        int viewCount = 0;
        if (stats != null) {
            viewCount = stats.getPlays();
        }

        if (viewCount > 0) {
            formattedViewCount = NumberFormat.getNumberInstance(Locale.US).format(viewCount);
            if (viewCount > 1) {
                formattedViewCount = String.format("%s views", formattedViewCount);
            } else {
                formattedViewCount = String.format("%s view", formattedViewCount);
            }
        }
        return formattedViewCount;
    }

    public String getFormattedCreatedTime(){
        String formattedCreatedTime = DateUtility.getFormattedDateAndTime(DateUtility.getCalendar(createdTime, PATTERN), DateUtility.FORMAT_RELATIVE);
        if(!TextUtils.isEmpty(formattedCreatedTime))
            formattedCreatedTime = String.format("Uploaded %s", formattedCreatedTime);
        return formattedCreatedTime;
    }

    public List<String> getCanonicalTags(){
        ArrayList<String> canonicalTags = new ArrayList<>();
        if (tags != null && tags.size() > 0) {
            for (Tag tag : tags) {
                String canonicalTag = tag.getCanonical();
                if (canonicalTag.length() > 0) {
                    canonicalTags.add(canonicalTag);
                }
            }
        }
        return canonicalTags;
    }

    public String getFormattedDescription(){
        String formattedDescription = "";
        if(!TextUtils.isEmpty(description))
            formattedDescription = description.trim();
        return formattedDescription;
    }
    // endregion

    // region Setters

    public void setUri(String uri) {
        this.uri = uri;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public void setDuration(int duration) {
        this.duration = duration;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setEmbed(Embed embed) {
        this.embed = embed;
    }

    public void setCreatedTime(String createdTime) {
        this.createdTime = createdTime;
    }

    public void setModifiedTime(String modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    public void setContentRating(List<String> contentRating) {
        this.contentRating = contentRating;
    }

    public void setPictures(Pictures pictures) {
        this.pictures = pictures;
    }

    public void setTags(List<Tag> tags) {
        this.tags = tags;
    }

    public void setStats(Stats stats) {
        this.stats = stats;
    }

    public void setMetadata(Metadata metadata) {
        this.metadata = metadata;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    // endregion

    // region Parcelable Methods
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.uri);
        dest.writeString(this.name);
        dest.writeString(this.description);
        dest.writeString(this.link);
        dest.writeInt(this.duration);
        dest.writeInt(this.width);
        dest.writeString(this.language);
        dest.writeInt(this.height);
        dest.writeParcelable(this.embed, flags);
        dest.writeString(this.createdTime);
        dest.writeString(this.modifiedTime);
        dest.writeStringList(this.contentRating);
        dest.writeParcelable(this.pictures, flags);
        dest.writeTypedList(this.tags);
        dest.writeParcelable(this.stats, flags);
        dest.writeParcelable(this.metadata, flags);
        dest.writeParcelable(this.user, flags);
        dest.writeString(this.status);
    }
    // endregion

    public static final Parcelable.Creator<Video> CREATOR = new Parcelable.Creator<Video>() {
        @Override
        public Video createFromParcel(Parcel source) {
            return new Video(source);
        }

        @Override
        public Video[] newArray(int size) {
            return new Video[size];
        }
    };
} public class VideoConfig {

    // region Fields
    @SerializedName("request")
    private Request request;
    // endregion

    // region Getters
    public Request getRequest() {
        return request;
    }

    public String getVideoUrl() {
        String videoUrl = "";

        if (request != null) {
            Files files = request.getFiles();
            if (files != null) {
                H264 h264 = files.getH264();
                HLS hls = files.getHls();
                VP6 vp6 = files.getVp6();
                List<ProgressiveData> progressiveDataList = files.getProgressive();

                String progressiveDataUrl = getProgressiveDataUrl(progressiveDataList);
                String h264VideoUrl = getH264VideoUrl(h264);
                String vp6VideoUrl = getVP6VideoUrl(vp6);
                String hlsVideoUrl = getHLSVideoUrl(hls);

                if (!TextUtils.isEmpty(progressiveDataUrl)) {
                    videoUrl = progressiveDataUrl;
                } else if (!TextUtils.isEmpty(h264VideoUrl)) {
                    videoUrl = h264VideoUrl;
                } else if (!TextUtils.isEmpty(vp6VideoUrl)) {
                    videoUrl = vp6VideoUrl;
                } else if (!TextUtils.isEmpty(hlsVideoUrl)) {
                    videoUrl = hlsVideoUrl;
                }
            }
        }

        return videoUrl;
    }

    private String getHLSVideoUrl(HLS hls) {
        String videoUrl = "";
        if (hls != null) {
            String url = hls.getUrl();
            videoUrl = url;
        }
        return videoUrl;
    }

    private String getProgressiveDataUrl(List<ProgressiveData> progressiveDataList){
        String progressiveDataUrl = "";

        String progessiveData270pUrl = "";
        String progessiveData360pUrl = "";
        String progessiveData1080pUrl = "";

        for(ProgressiveData progressiveData : progressiveDataList){
            String quality = progressiveData.getQuality();
            switch (quality) {
                case "1080p":
                    progessiveData1080pUrl = progressiveData.getUrl();
                    break;
                case "360p":
                    progessiveData360pUrl = progressiveData.getUrl();
                    break;
                case "270p":
                    progessiveData270pUrl = progressiveData.getUrl();
                    break;
            }
        }

        if(!TextUtils.isEmpty(progessiveData1080pUrl)){
            progressiveDataUrl = progessiveData1080pUrl;
        } else if(!TextUtils.isEmpty(progessiveData360pUrl)){
            progressiveDataUrl = progessiveData360pUrl;
        } else if(!TextUtils.isEmpty(progessiveData270pUrl)){
            progressiveDataUrl = progessiveData270pUrl;
        }

        return progressiveDataUrl;
    }

    private String getH264VideoUrl(H264 h264) {
        String videoUrl = "";
        if (h264 != null) {

            VideoFormat hdVideoFormat = h264.getHd();
            VideoFormat sdVideoFormat = h264.getSd();
            VideoFormat mobileVideoFormat = h264.getMobile();

            int width = -1;
            int height = -1;
            if (hdVideoFormat != null) {
                videoUrl = hdVideoFormat.getUrl();
                width = hdVideoFormat.getWidth();
                height = hdVideoFormat.getHeight();
            } else if (sdVideoFormat != null) {
                videoUrl = sdVideoFormat.getUrl();
                width = sdVideoFormat.getWidth();
                height = sdVideoFormat.getHeight();
            } else if (mobileVideoFormat != null) {
                videoUrl = mobileVideoFormat.getUrl();
                width = mobileVideoFormat.getWidth();
                height = mobileVideoFormat.getHeight();
            }

            Timber.d(String.format("mGetVideoConfigCallback : url - %s", videoUrl));
            Timber.d(String.format("mGetVideoConfigCallback : width - %d : height - %d", width, height));
        }

        return videoUrl;
    }

    private String getVP6VideoUrl(VP6 vp6) {
        String videoUrl = "";
        if (vp6 != null) {

            VideoFormat hdVideoFormat = vp6.getHd();
            VideoFormat sdVideoFormat = vp6.getSd();
            VideoFormat mobileVideoFormat = vp6.getMobile();

            int width = -1;
            int height = -1;
            if (hdVideoFormat != null) {
                videoUrl = hdVideoFormat.getUrl();
                width = hdVideoFormat.getWidth();
                height = hdVideoFormat.getHeight();
            } else if (sdVideoFormat != null) {
                videoUrl = sdVideoFormat.getUrl();
                width = sdVideoFormat.getWidth();
                height = sdVideoFormat.getHeight();
            } else if (mobileVideoFormat != null) {
                videoUrl = mobileVideoFormat.getUrl();
                width = mobileVideoFormat.getWidth();
                height = mobileVideoFormat.getHeight();
            }

            Timber.d(String.format("mGetVideoConfigCallback : url - %s", videoUrl));
            Timber.d(String.format("mGetVideoConfigCallback : width - %d : height - %d", width, height));
        }

        return videoUrl;
    }
    // endregion

    // region Setters
    public void setRequest(Request request) {
        this.request = request;
    }
    // endregion
} public class LoopPrefs {

    // region Constants
    private static final String LOOP_PREF = "LOOP_PREF";
    private static final String KEY_ACCESS_TOKEN = "KEY_ACCESS_TOKEN";
    private static final String KEY_AUTHORIZED_USER = "KEY_AUTHORIZED_USER";
    // endregion

    // region Constructors
    private LoopPrefs() {
        //no instance
    }

    // region Getters
    public static AccessToken getAccessToken(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        Gson gson = new Gson();
        String json = preferences.getString(KEY_ACCESS_TOKEN, "");
        AccessToken accessToken = gson.fromJson(json, AccessToken.class);
        return accessToken;
    }

    public static AuthorizedUser getAuthorizedUser(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        Gson gson = new Gson();
        String json = preferences.getString(KEY_AUTHORIZED_USER, "");
        AuthorizedUser authorizedUser = gson.fromJson(json, AuthorizedUser.class);
        return authorizedUser;
    }
    // endregion

    // region Setters
    public static void saveAccessToken(Context context, AccessToken accessToken) {
        SharedPreferences.Editor editor = getEditor(context);
        Gson gson = new Gson();
        String json = gson.toJson(accessToken);
        editor.putString(KEY_ACCESS_TOKEN, json)
            .apply();
    }

    public static void saveAuthorizedUser(Context context, AuthorizedUser authorizedUser) {
        SharedPreferences.Editor editor = getEditor(context);
        Gson gson = new Gson();
        String json = gson.toJson(authorizedUser);
        editor.putString(KEY_AUTHORIZED_USER, json)
            .apply();
    }
    // endregion

    public static void signOut(Context context) {
        SharedPreferences.Editor editor = getEditor(context);
        editor.remove(KEY_ACCESS_TOKEN)
            .remove(KEY_AUTHORIZED_USER)
            .apply();
    }

    // region Helper Methods
    private static SharedPreferences.Editor getEditor(Context context) {
        SharedPreferences preferences = getSharedPreferences(context);
        return preferences.edit();
    }

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(LOOP_PREF, Context.MODE_PRIVATE);
    }
    // endregion
} public class FontCache {

    private static Map<String, Typeface> fontCache = new HashMap<>(8);

    public static Typeface getTypeface(String fontname, Context context) {
        Typeface typeface = fontCache.get(fontname);

        if (typeface == null) {
            try {
                typeface = Typeface.createFromAsset(context.getAssets(), String.format("fonts/%s", fontname));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }

            fontCache.put(fontname, typeface);
        }

        return typeface;
    }
} public class NetworkLogUtility {

    public static void logFailure(Call call, Throwable throwable){
        if(call != null){
            if (call.isCanceled())
                Timber.e("Request was cancelled");

//            Request request = call.request();
//            if(request != null){
//                HttpUrl httpUrl = request.url();
//                if(httpUrl != null){
//                    Timber.e(String.format("logFailure() : %s : failed", httpUrl));
//                }
//            }
        }

        if(throwable != null){
            Throwable cause = throwable.getCause();
//            String message = throwable.getMessage();

            if (cause != null) {
                Timber.e(String.format("logFailure() : cause.toString() : %s", cause.toString()));
            }

//            if (!TextUtils.isEmpty(message)) {
//                Timber.e(String.format("logFailure() : message : %s", message));
//            }

//            throwable.printStackTrace();
        }
    }
} public class NetworkUtility {

    public static boolean isNetworkAvailable(Context context){
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = cm.getActiveNetworkInfo();
        return networkInfo != null && networkInfo.isConnected();
    }

    public static boolean isKnownException(Throwable t){
        return (t instanceof ConnectException
                || t instanceof UnknownHostException
                || t instanceof SocketTimeoutException
                || t instanceof IOException);
    }

} public class TrestleUtility {

    public static CharSequence getFormattedText(String text, Typeface font) {
        return Trestle.getFormattedText(
                new Span.Builder(text)
                        .typeface(font)
                        .build());
    }

    public static CharSequence getFormattedText(String text, Typeface font, int size) {
        return Trestle.getFormattedText(
                new Span.Builder(text)
                        .typeface(font)
                        .absoluteSize(size)
                        .build());
    }
}
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.MediaRouteButton;
import android.support.v7.view.ContextThemeWrapper;
import android.support.v7.widget.Toolbar;
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.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.etiennelawlor.loop.LoopApplication;
import com.etiennelawlor.loop.R;
import com.etiennelawlor.loop.models.AccessToken;
import com.etiennelawlor.loop.models.VideoSavedState;
import com.etiennelawlor.loop.network.ServiceGenerator;
import com.etiennelawlor.loop.network.VimeoPlayerService;
import com.etiennelawlor.loop.network.interceptors.AuthorizedNetworkInterceptor;
import com.etiennelawlor.loop.network.models.response.Video;
import com.etiennelawlor.loop.network.models.response.VideoConfig;
import com.etiennelawlor.loop.prefs.LoopPrefs;
import com.etiennelawlor.loop.utilities.FontCache;
import com.etiennelawlor.loop.utilities.NetworkLogUtility;
import com.etiennelawlor.loop.utilities.NetworkUtility;
import com.etiennelawlor.loop.utilities.TrestleUtility;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.common.images.WebImage;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
package com.etiennelawlor.loop.fragments;






/**
 * Created by etiennelawlor on 1/3/17.
 */

public class VideoDetailsFragment extends BaseFragment {

    // ExoPlayer Guide
    // https://google.github.io/ExoPlayer/guide.html
    // https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/ui/SimpleExoPlayerView.html

    // region Constants
    private static final int VIDEO_SHARE_REQUEST_CODE = 1002;
    public static final String KEY_VIDEO_ID = "KEY_VIDEO_ID";
    public static final String KEY_VIDEO = "KEY_VIDEO";
    // endregion

    // region Views
    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @Nullable
    @BindView(R.id.viewpager)
    ViewPager viewPager;
    @Nullable
    @BindView(R.id.tabs)
    TabLayout tabLayout;
    @BindView(R.id.sepv)
    SimpleExoPlayerView simpleExoPlayerView;
//    @BindView(R.id.loading_iv)
//    LoadingImageView loadingImageView;
//    @BindView(R.id.error_ll)
//    LinearLayout errorLinearLayout;
//    @BindView(R.id.error_tv)
//    TextView errorTextView;
    @BindView(R.id.exo_thumbnail)
    ImageView thumbnailImageView;
    @BindView(R.id.cast_info_tv)
    TextView castInfoTextView;
    @BindView(R.id.exo_shutter)
    View shutterView;
    @BindView(R.id.exo_progress)
    SeekBar exoSeekBar;
    @BindView(R.id.exo_play)
    ImageButton exoPlayButton;
    @BindView(R.id.exo_pause)
    ImageButton exoPauseButton;
    @BindView(R.id.exo_replay)
    ImageButton replayImageButton;
    @BindView(R.id.exo_btns_fl)
    FrameLayout exoButtonsFrameLayout;
    @BindView(R.id.control_view_ll)
    LinearLayout controlViewLinearLayout;
    @BindView(R.id.mrb)
    MediaRouteButton mediaRouteButton;
    @BindView(R.id.exo_content_frame)
    AspectRatioFrameLayout aspectRatioFrameLayout;
    // endregion

    // region Member Variables
    private Video video;