public class EventMapKeys {
    public static final String NAME = "name";
    public static final String DURATION = "duration";
    public static final String VIDEO_ID = "video_id";
} public class EventNames {
    public static final String VIDEO_DISLIKED = "video_disliked";
    public static final String VIDEO_LIKED = "video_liked";

} 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 SearchableActivity extends AppCompatActivity {

    // region Member Variables
    private CompositeSubscription compositeSubscription;
    // endregion

    // region Lifecycle Methods
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_searchable);
        ButterKnife.bind(this);

        if (Intent.ACTION_SEARCH.equals(getIntent().getAction())
                ||  SearchIntents.ACTION_SEARCH.equals(getIntent().getAction())) {
            Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.content_fl);
            if(fragment == null){
                fragment = SearchableFragment.newInstance(getIntent().getExtras());
                getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.content_fl, fragment, "")
                        .commit();
            } else {
                getSupportFragmentManager()
                        .beginTransaction()
                        .attach(fragment)
                        .commit();
            }
        }

        compositeSubscription = new CompositeSubscription();

        setUpRxBusSubscription();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        compositeSubscription.unsubscribe();
    }
    // endregion

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                supportFinishAfterTransition();
//                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.content_fl);
        if (fragment != null) {
            fragment.onActivityResult(requestCode, resultCode, data);
        }
    }

    // region Helper Methods
    private void setUpRxBusSubscription(){
        Subscription rxBusSubscription = RxBus.getInstance().toObserverable()
                .observeOn(AndroidSchedulers.mainThread()) // UI Thread
                .subscribe(new Action1<Object>() {
                    @Override
                    public void call(Object event) {
                        if (event == null) {
                            return;
                        }

                        if(event instanceof LeftDrawableClickedEvent){
                            LeftDrawableClickedEvent.Type type = ((LeftDrawableClickedEvent)event).getType();

                            if(type == LeftDrawableClickedEvent.Type.BACK)
                                finish();
//            onBackPressed();
                        }
                    }
                });

        compositeSubscription.add(rxBusSubscription);
    }
    // endregion
} public class VideoCommentsActivity extends AppCompatActivity {

    // region Lifecycle Methods
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_comments);
        ButterKnife.bind(this);

        Fragment fragment = getSupportFragmentManager().findFragmentById(android.R.id.content);
        if(fragment == null){
            fragment = VideoCommentsFragment.newInstance(getIntent().getExtras());
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(android.R.id.content, fragment, "")
                    .commit();
        } else {
            getSupportFragmentManager()
                    .beginTransaction()
                    .attach(fragment)
                    .commit();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
    // endregion

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                supportFinishAfterTransition();
//                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

} public class Event {

    // region Member Variables
    private String name;
    private HashMap<String, Object> map;
    // endregion

    // region Constructors
    public Event(String name, HashMap<String, Object> map) {
        this.name = name;
        this.map = map;
    }
    // endregion

    // region Getters
    public String getName(){
        return name;
    }

    public HashMap<String, Object> getMap(){
        return map;
    }
    // endregion

} public class EventLogger {

    public static void logEvent(Event event){
//        logFabricEvent(event);
        logFlurryEvent(event);
//        logGoogleAnalyticsEvent(event);
    }

//    private static void logFabricEvent(Event event){
//        String name = event.getName();
//        HashMap<String, Object> map = event.getMap();
//
//        CustomEvent customEvent = new CustomEvent(name);
//
//        for (Object o : map.entrySet()) {
//            Map.Entry pair = (Map.Entry) o;
//
//            String key = (String) pair.getKey();
//            Object value = pair.getValue();
//
//            if (value instanceof Number) {
//                customEvent.putCustomAttribute(key, (Number) value);
//            } else if (value instanceof String) {
//                customEvent.putCustomAttribute(key, (String) value);
//            }
//        }
//
//        Answers.getInstance().logCustom(customEvent);
//    }

    private static void logFlurryEvent(Event event){
        String name = event.getName();
        HashMap<String, Object> map = event.getMap();

        Map<String, String> flurryMap = new HashMap<>();

        for (Object o : map.entrySet()) {
            Map.Entry pair = (Map.Entry) o;

            String key = (String) pair.getKey();
            Object value = pair.getValue();

            flurryMap.put(key, String.valueOf(value));
        }


        FlurryAgent.logEvent(name, flurryMap);
    }

    private static void logGoogleAnalyticsEvent(Event event) {
        String name = event.getName();
        HashMap<String, Object> map = event.getMap();
    }
} public class RxBus {

    private static final PublishSubject<Object> bus = PublishSubject.create();
//    private static final BehaviorSubject<Object> bus = BehaviorSubject.create();


    // If multiple threads are going to emit events to this
    // then it must be made thread-safe like this instead
//    private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());

    public void send(Object o) {
        bus.onNext(o);
    }

    public Observable<Object> toObserverable() {
        return bus;
    }

    public boolean hasObservers() {
        return bus.hasObservers();
    }

    public static RxBus getInstance() {
        return new RxBus();
    }

    private RxBus() {
        // No instances.
    }
} public class LikeVideoClickedEvent {
} public class SearchPerformedEvent {

    // region Member Variables
    private String query;
    // endregion

    // region Constructors
    public SearchPerformedEvent(String query){
        this.query = query;
    }
    // endregion

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.query = query;
    }
} public class WatchLaterClickedEvent {
} 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 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 VimeoService {

    String BASE_URL = "https://api.vimeo.com/";

    @FormUrlEncoded
    @POST("oauth/access_token")
    Call<OAuthResponse> exchangeCode(@Field("grant_type") String grantType,
                                     @Field("code") String code,
                                     @Field("redirect_uri") String redirectUri);

    @GET("videos")
    Call<VideosEnvelope> findVideos(@Query("query") String query,
                                    @Query("sort") String sort,
                                    @Query("direction") String direction,
                                    @Query("page") Integer page,
                                    @Query("per_page") Integer perPage,
                                    @Query("filter") String filter);

    @GET("videos/{videoId}/comments")
    Call<CommentsEnvelope> getComments(@Path("videoId") Long videoId,
                                       @Query("sort") String sort,
                                       @Query("direction") String direction,
                                       @Query("page") Integer page,
                                       @Query("per_page") Integer perPage);

    @POST("videos/{videoId}/comments")
    Call<Comment> addComment(@Path("videoId") Long videoId,
                             @Body CommentPost commentPost);

    @DELETE("videos/{videoId}/comments/{commentId}")
    Call<ResponseBody> deleteComment(@Path("videoId") Long videoId,
                               @Path("commentId") Long commentId);

    @GET("videos/{videoId}/videos?filter=related")
    Call<VideosEnvelope> findRelatedVideos(@Path("videoId") Long videoId,
                                           @Query("page") Integer page,
                                           @Query("per_page") Integer perPage);

    @GET("users/{userId}/videos")
    Call<VideosEnvelope> getUserVideos(@Path("userId") Long userId,
                                           @Query("page") Integer page,
                                           @Query("per_page") Integer perPage);

    @GET("categories")
    Call<CategoriesEnvelope> getCategories();

    @GET("me/likes")
    Call<VideosEnvelope> findLikedVideos(@Query("query") String query,
                                         @Query("sort") String sort,
                                         @Query("direction") String direction,
                                         @Query("page") Integer page,
                                         @Query("per_page") Integer perPage);

    @GET("me/watchlater")
    Call<VideosEnvelope> findWatchLaterVideos(@Query("query") String query,
                                              @Query("sort") String sort,
                                              @Query("direction") String direction,
                                              @Query("page") Integer page,
                                              @Query("per_page") Integer perPage);

    @GET("me/feed")
    Call<FeedItemsEnvelope> findMyFeedVideos(@Query("page") Integer page,
                                             @Query("per_page") Integer perPage);

    @PUT("me/likes/{videoId}")
    Call<ResponseBody> likeVideo(@Path("videoId") String videoId);


    @DELETE("me/likes/{videoId}")
    Call<ResponseBody> unlikeVideo(@Path("videoId") String videoId);

    @PUT("me/watchlater/{videoId}")
    Call<ResponseBody> addVideoToWatchLater(@Path("videoId") String videoId);


    @DELETE("me/watchlater/{videoId}")
    Call<ResponseBody> removeVideoFromWatchLater(@Path("videoId") String 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 User implements Parcelable {

    // region Fields
    @SerializedName("uri")
    private String uri;
    @SerializedName("name")
    private String name;
    @SerializedName("link")
    private String link;
    @SerializedName("location")
    private String location;
    @SerializedName("bio")
    private String bio;
    @SerializedName("created_time")
    private String createdTime;
    @SerializedName("account")
    private String account;
    @SerializedName("pictures")
    private Pictures pictures;
    // endregion

    // region Constructors
    public User() {
    }

    protected User(Parcel in) {
        this.uri = in.readString();
        this.name = in.readString();
        this.link = in.readString();
        this.location = in.readString();
        this.bio = in.readString();
        this.createdTime = in.readString();
        this.account = in.readString();
        this.pictures = in.readParcelable(Pictures.class.getClassLoader());
    }
    // endregion

    // region Getters

    public String getUri() {
        return uri;
    }

    public String getName() {
        return name;
    }

    public String getLink() {
        return link;
    }

    public String getLocation() {
        return location;
    }

    public String getBio() {
        return bio;
    }

    public String getCreatedTime() {
        return createdTime;
    }

    public String getAccount() {
        return account;
    }

    public Pictures getPictures() {
        return pictures;
    }

    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 getAvatarUrl(){
        String avatarUrl =  "";
        if (pictures != null) {
            List<Size> sizes = pictures.getSizes();
            if (sizes != null && sizes.size() > 0) {
                Size size = sizes.get(sizes.size() - 1);
                if (size != null) {
                    avatarUrl = size.getLink();
                }
            }
        }
        return avatarUrl;
    }

    // endregion

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

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

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

    public void setLocation(String location) {
        this.location = location;
    }

    public void setBio(String bio) {
        this.bio = bio;
    }

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

    public void setAccount(String account) {
        this.account = account;
    }

    public void setPictures(Pictures pictures) {
        this.pictures = pictures;
    }
    // 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.link);
        dest.writeString(this.location);
        dest.writeString(this.bio);
        dest.writeString(this.createdTime);
        dest.writeString(this.account);
        dest.writeParcelable(this.pictures, flags);
    }
    // endregion

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

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
} 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 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 AvatarView extends FrameLayout {

    // region Constants
    // endregion

    // region Member Variables
    private Transformation roundedTransformation;
    private float radius;
    // endregion

    // region Views
    @BindView(R.id.initials_tv)
    TextView textView;
    @BindView(R.id.photo_iv)
    ImageView imageView;
    // endregion

    // region Interfaces
    public interface UserInitials {
        String getInitials();
    }
    // endregion

    // region Constructors
    public AvatarView(Context context) {
        super(context);
        init(context, null);
    }

    public AvatarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }
    // endregion

    // region Helper Methods
    private void init(Context context, AttributeSet attrs) {
        if (!isInEditMode()) {
            LayoutInflater.from(getContext()).inflate(R.layout.avatar_view, this, true);
            ButterKnife.bind(this);

            if(attrs != null){
                TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarView, 0, 0);
                try {
                    radius = a.getDimension(R.styleable.AvatarView_av_radius, 0);
                } finally {
                    a.recycle();
                }
            }

            roundedTransformation = new RoundedTransformationBuilder()
                    .cornerRadius(radius)
//                .borderWidthDp(1f)
//                .borderColor(ContextCompat.getColor(getContext(), R.color.grey_400))
                    .oval(false)
                    .build();

            // resize ImageView
            ViewGroup.LayoutParams imageViewLayoutParams = imageView.getLayoutParams();
            imageViewLayoutParams.width = (int)(radius*2);
            imageViewLayoutParams.height = (int)(radius*2);
            imageView.setLayoutParams(imageViewLayoutParams);

            // resize TextView
            ViewGroup.LayoutParams textViewLayoutParams = textView.getLayoutParams();
            textViewLayoutParams.width = (int)(radius*2);
            textViewLayoutParams.height = (int)(radius*2);
            textView.setLayoutParams(textViewLayoutParams);

            // resize TextView's fontSize
            int fontSize = (16*(int)radius*2)/ DisplayUtility.dp2px(getContext(), 40);
            textView.setTextSize(fontSize);
        }
    }

    public void bind(@Nullable User user) {
        if (user == null) {
            return;
        }

        String avatarUrl = user.getAvatarUrl();

        if (!TextUtils.isEmpty(avatarUrl)) {
            Picasso.with(getContext())
                    .load(avatarUrl)
                    .transform(roundedTransformation)
                    .resize((int)radius*2, (int)radius*2)
                    .centerCrop()
                    .into(imageView);
        } else {
            nullify();
        }

//        textView.setText(user.getInitials());
        String firstInital = "";
        String name = user.getName();
        if(!TextUtils.isEmpty(name))
            firstInital = String.valueOf(name.charAt(0)).toUpperCase().trim();
        textView.setText(firstInital);

        // Set up background
        StateListDrawable stateListDrawable = new StateListDrawable();

        GradientDrawable defaultDrawable = (GradientDrawable) ContextCompat.getDrawable(getContext(), R.drawable.default_avatar_bg);
        defaultDrawable.setColor(DisplayUtility.getDefaultAvatarBackgroundColor(user, getContext()));
        if(isClickable()){
            GradientDrawable pressedDrawable = (GradientDrawable) ContextCompat.getDrawable(getContext(), R.drawable.pressed_avatar_bg);
            pressedDrawable.setColor(DisplayUtility.getPressedAvatarBackgroundColor(user, getContext()));
            stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressedDrawable);
        }
        stateListDrawable.addState(StateSet.WILD_CARD, defaultDrawable);
        textView.setBackground(stateListDrawable);
    }

    public void nullify() {
        imageView.setImageDrawable(null);
        textView.setText("");
    }
    // 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 Transformers {

    public static final HashtagView.DataTransform<String> HASH = new HashtagView.DataTransform<String>() {
        @Override
        public CharSequence prepare(String item) {
            SpannableString spannableString = new SpannableString("#" + item);
            spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#85F5F5F5")), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            return spannableString;
        }
    };

    public static final HashtagView.DataTransform<String> PERSON = new HashtagView.DataTransform<String>() {
        @Override
        public CharSequence prepare(String item) {
            SpannableString spannableString = new SpannableString("@" + item);
            spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#85F5F5F5")), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            return spannableString;
        }
    };

    public static final HashtagView.DataTransform<String> CAPS = new HashtagView.DataTransform<String>() {
        @Override
        public CharSequence prepare(String item) {
            return item.toUpperCase();
        }
    };

    public static final HashtagView.DataTransform<String> SPAN1 = new HashtagView.DataTransform<String>() {
        @Override
        public CharSequence prepare(String item) {
            SpannableString spannableString = new SpannableString("#" + item);
            spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#85F5F5F5")), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            spannableString.setSpan(new SuperscriptSpan(), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            return spannableString;
        }
    };

    public static final HashtagView.DataTransform<String> SPAN2 = new HashtagView.DataTransform<String>() {
        @Override
        public CharSequence prepare(String item) {
            SpannableString spannableString = new SpannableString("@" + item);
            spannableString.setSpan(new ForegroundColorSpan(Color.parseColor("#33691E")), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            spannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            spannableString.setSpan(new RelativeSizeSpan(1.2f), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            return spannableString;
        }
    };

} 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.SearchManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.etiennelawlor.loop.EventMapKeys;
import com.etiennelawlor.loop.EventNames;
import com.etiennelawlor.loop.LoopApplication;
import com.etiennelawlor.loop.R;
import com.etiennelawlor.loop.activities.SearchableActivity;
import com.etiennelawlor.loop.activities.VideoCommentsActivity;
import com.etiennelawlor.loop.analytics.Event;
import com.etiennelawlor.loop.analytics.EventLogger;
import com.etiennelawlor.loop.bus.RxBus;
import com.etiennelawlor.loop.bus.events.LikeVideoClickedEvent;
import com.etiennelawlor.loop.bus.events.SearchPerformedEvent;
import com.etiennelawlor.loop.bus.events.WatchLaterClickedEvent;
import com.etiennelawlor.loop.models.AccessToken;
import com.etiennelawlor.loop.network.ServiceGenerator;
import com.etiennelawlor.loop.network.VimeoService;
import com.etiennelawlor.loop.network.interceptors.AuthorizedNetworkInterceptor;
import com.etiennelawlor.loop.network.models.response.User;
import com.etiennelawlor.loop.network.models.response.Video;
import com.etiennelawlor.loop.prefs.LoopPrefs;
import com.etiennelawlor.loop.ui.AvatarView;
import com.etiennelawlor.loop.utilities.FontCache;
import com.etiennelawlor.loop.utilities.NetworkLogUtility;
import com.etiennelawlor.loop.utilities.NetworkUtility;
import com.etiennelawlor.loop.utilities.Transformers;
import com.etiennelawlor.loop.utilities.TrestleUtility;
import com.greenfrvr.hashtagview.HashtagView;
import java.util.HashMap;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.Unbinder;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.subscriptions.CompositeSubscription;

                        isLikeOn = true;
                        likeImageView.setImageResource(R.drawable.ic_likes_on);

                        break;
                    case 400:
                        // If the video is owned by the authenticated user
                        break;
                    case 403:
                        // If the authenticated user is not allowed to like videos
                        break;
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            NetworkLogUtility.logFailure(call, t);

            if (!call.isCanceled()){
                if(NetworkUtility.isKnownException(t)){
                    Snackbar.make(getActivity().findViewById(R.id.main_content),
                            TrestleUtility.getFormattedText("Network connection is unavailable.", font, 16),
                            Snackbar.LENGTH_LONG)
                            .show();
                }
            }
        }
    };

    private Callback<ResponseBody> unlikeVideoCallback = new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

            if (!response.isSuccessful()) {
                int responseCode = response.code();
                if(responseCode == 504) { // 504 Unsatisfiable Request (only-if-cached)
//                    errorTextView.setText("Can't load data.\nCheck your network connection.");
//                    errorLinearLayout.setVisibility(View.VISIBLE);
                }
                return;
            }

            okhttp3.Response rawResponse = response.raw();
            if (rawResponse != null) {
                int code = rawResponse.code();
                switch (code) {
                    case 204:
                        // No Content
                        RxBus.getInstance().send(new LikeVideoClickedEvent());

                        HashMap<String, Object> map = new HashMap<>();
                        map.put(EventMapKeys.NAME, video.getName());
                        map.put(EventMapKeys.DURATION, video.getDuration());
                        map.put(EventMapKeys.VIDEO_ID, video.getId());

                        Event event = new Event(EventNames.VIDEO_DISLIKED, map);
                        EventLogger.logEvent(event);

                        isLikeOn = false;
                        likeImageView.setImageResource(R.drawable.ic_likes_off);
                        break;
                    case 403:
                        // If the authenticated user is not allowed to like videos
                        break;
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            NetworkLogUtility.logFailure(call, t);

            if (!call.isCanceled()){
                if(NetworkUtility.isKnownException(t)){
                    Snackbar.make(getActivity().findViewById(R.id.main_content),
                            TrestleUtility.getFormattedText("Network connection is unavailable.", font, 16),
                            Snackbar.LENGTH_LONG)
                            .show();
                }
            }
        }
    };
    // endregion

    // region Constructors
    public VideoDetailsInfoFragment() {
    }
    // endregion

    // region Factory Methods
    public static VideoDetailsInfoFragment newInstance(Bundle extras) {
        VideoDetailsInfoFragment fragment = new VideoDetailsInfoFragment();
        fragment.setArguments(extras);
        return fragment;
    }

    public static VideoDetailsInfoFragment newInstance() {
        VideoDetailsInfoFragment fragment = new VideoDetailsInfoFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }
    // endregion

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

        if (getArguments() != null) {
            video = (Video) getArguments().get(LikedVideosFragment.KEY_VIDEO);
        }

        AccessToken token = LoopPrefs.getAccessToken(getActivity());
        vimeoService = ServiceGenerator.createService(
                VimeoService.class,
                VimeoService.BASE_URL,
                new AuthorizedNetworkInterceptor(token));

        boldFont = FontCache.getTypeface("Ubuntu-Bold.ttf", LoopApplication.getInstance().getApplicationContext());