public class NetworkManager {

    private ConnectivityManager connectivityManager;

    public NetworkManager(ConnectivityManager connectivityManager) {
        this.connectivityManager = connectivityManager;
    }

    public boolean isConnectedToInternet(){
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        return networkInfo !=null && networkInfo.isConnected();
    }
} public class RxEventBus {

    private final PublishSubject<Object> busSubject;

    public RxEventBus() {
        busSubject = PublishSubject.create();
    }

    public void post(Object event) {
        busSubject.onNext(event);
    }

    public Observable<Object> observable() {
        return busSubject;
    }

    public <T> Observable<T> filteredObservable(final Class<T> eventClass) {
        return busSubject.filter(new Func1<Object, Boolean>() {
            @Override
            public Boolean call(Object event) {
                return eventClass.isInstance(event);
            }
        }).map(new Func1<Object, T>() {
            //Safe to cast because of the previous filter
            @SuppressWarnings("unchecked")
            @Override
            public T call(Object event) {
                return (T) event;
            }
        });
    }
} public class PlanketDatabase {

    private final String USER_ID_FIELD_NAME = "userId";
    private final String PHOTO_ID_FIELD_NAME = "photoId";

    private Context context;
    private RealmConfiguration realmConfiguration;

    public PlanketDatabase(Context context) {
        this.context = context;
        setup();
    }

    private Realm getRealmInstance() {
        return Realm.getDefaultInstance();
    }

    private void setup() {
        if (realmConfiguration == null) {
            Realm.init(context);
            realmConfiguration = new RealmConfiguration.Builder().build();
            Realm.setDefaultConfiguration(realmConfiguration);
        } else {
            throw new IllegalStateException("PlanketDatabase already configured");
        }
    }


    public void deleteUserFav(String userId, final String photoId){
        Realm realm = getRealmInstance();
        final RealmResults<UserFavePhotoId> userFavePhotoIdsResults =
                realm.where(UserFavePhotoId.class)
                .equalTo(USER_ID_FIELD_NAME,userId)
                .equalTo(PHOTO_ID_FIELD_NAME, photoId)
                .findAll();

        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                userFavePhotoIdsResults.deleteAllFromRealm();
            }
        });
    }

    public void addUserFav(final String userId, final Set<String> userFavPhotos) {
        Realm realm = getRealmInstance();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                UserFavePhotoId userFavePhotoId;
                for (String photoId : userFavPhotos){
                    if(existInRealm(realm, userId, photoId)) {
                        userFavePhotoId = realm.createObject(UserFavePhotoId.class);
                        userFavePhotoId.userId = userId;
                        userFavePhotoId.photoId = photoId;
                    }
                }
            }
        });
    }

    public void addUserFav(final String userId, final String photoId) {
        Realm realm = getRealmInstance();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                if(existInRealm(realm, userId, photoId)) {
                    UserFavePhotoId userFavePhotoId = realm.createObject(UserFavePhotoId.class);
                    userFavePhotoId.userId = userId;
                    userFavePhotoId.photoId = photoId;
                }
            }
        });
    }

    public Set<String> loadAllUserFaves(String userId) {
        RealmResults<UserFavePhotoId>  userFavePhotoIdsResults = getRealmInstance()
                .where(UserFavePhotoId.class)
                .equalTo(USER_ID_FIELD_NAME, userId)
                .findAll();

        Set<String> userFavPhotos = new HashSet();
        for (UserFavePhotoId userFavePhotoId : userFavePhotoIdsResults){
            userFavPhotos.add(userFavePhotoId.photoId);
        }
        return userFavPhotos;
    }

    public void close() {
        getRealmInstance().close();
    }

    private boolean existInRealm(Realm realm, String userId, String photoId) {
        return realm.where(UserFavePhotoId.class)
                .equalTo(USER_ID_FIELD_NAME, userId)
                .equalTo(PHOTO_ID_FIELD_NAME, photoId)
                .findFirst() == null;
    }

} public class PaginatedDataManager {

    public static final int FIRST_PAGE_DEFAULT = 1;
    private int currentPage;
    private int totalPages = -1;


    public PaginatedDataManager() {
    }

    public void resetPage() {
        this.currentPage = 0;
    }

    public int getNextPageToLoad() {
        return currentPage + 1;
    }

    public int addPageLoaded() {
        int pageLoaded = getNextPageToLoad();
        currentPage = pageLoaded;
        return pageLoaded;
    }


    public boolean areTotalPagesLoaded(){
        if (totalPages > 0 && getNextPageToLoad() > totalPages){
            return true;
        }
        return false;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }

    public int getTotalPages() {
        return totalPages;
    }

    public boolean isLoadingFirstPage(){
        return getNextPageToLoad() == PaginatedDataManager.FIRST_PAGE_DEFAULT;
    }
} public interface FlickrOauthService {

    String ENDPOINT = "https://www.flickr.com/services/";

    @POST("oauth/request_token")
    Single<ResponseBody> getRequestToken(@Query("oauth_callback") String oauthCallback);

    @GET("oauth/access_token")
    Single<ResponseBody> getAccessToken(@Query("oauth_token")String oauthToken, @Query("oauth_verifier")String oauthVerifier);

} public interface FlickrService {

    String ENDPOINT = "https://api.flickr.com/services/rest/";
    String PARAM_API_KEY = "api_key";
    String PARAM_FORMAT = "format";
    String PARAM_JSONCALLBACK = "nojsoncallback";
    String EXTRAS_URL = "url_s,url_z,tags,count_faves,count_comments,icon_server,owner_name,date_upload,geo,description";


    @GET("?method=flickr.interestingness.getList&per_page=100&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> searchInterestingness(@Query("page") int page);

    @GET("?method=flickr.photos.search&per_page=100&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> searchPhotosWithTag(@Query("tags") String tag, @Query("page") int page);

    @GET("?method=flickr.photos.search&per_page=100&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> searchPhotosWithText(@Query("text") String text, @Query("page") int page);

    @GET("?method=flickr.people.getPhotos&per_page=100&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> searchPhotosForUser(@Query("user_id") String userId, @Query("page") int page);

    @GET("?method=flickr.favorites.getList&per_page=100&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> searchFavesForUser(@Query("user_id") String userId, @Query("page") int page);

    @GET("?method=flickr.people.getPhotos&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> getUserPhotosForShowcase(@Query("user_id") String userId, @Query("per_page") int count);

    @GET("?method=flickr.favorites.getList&extras="+EXTRAS_URL)
    Single<SearchPhotosResponse> getUserFavesForShowcase(@Query("user_id") String userId, @Query("per_page") int count);

    @GET("?method=flickr.photos.getFavorites&per_page=50")
    Single<FavesResponse> getFaves(@Query("photo_id") String photoId, @Query("page") int page);

    @GET("?method=flickr.photos.comments.getList&per_page=50")
    Single<CommentsResponse> getComments(@Query("photo_id") String photoId, @Query("page") int page);

    @GET("?method=flickr.people.getInfo")
    Single<UserInfoResponse> getUserInfo(@Query("user_id") String userId);

    @GET("?method=flickr.favorites.getList&per_page=500")
    Observable<SearchPhotosResponse> getAllFaves(@Query("user_id") String userId, @Query("page") int page);

    @POST("?method=flickr.favorites.add")
    Single<StatusResponse> addPhotoFavorite(@Query("photo_id") String photoId);

    @POST("?method=flickr.favorites.remove")
    Single<StatusResponse> removePhotoFavorite(@Query("photo_id") String photoId);


} public interface OAuthManager {

    Single<String> getRequestToken();

    Single<String> getAccessToken(String oauthToken, String oauthVerifier);

} public class OAuthManagerImpl implements OAuthManager{

    private FlickrOauthService flickrOauthService;
    private OkHttpOAuthConsumer okHttpOAuthConsumer;
    private OAuthToken requestToken;
    private PlanketBoxPreferences planketBoxPreferences;
    private String callbackScheme;

    public OAuthManagerImpl(FlickrOauthService flickrOauthService, OkHttpOAuthConsumer okHttpOAuthConsumer, PlanketBoxPreferences planketBoxPreferences, Context context) {
        this.flickrOauthService = flickrOauthService;
        this.okHttpOAuthConsumer = okHttpOAuthConsumer;
        this.planketBoxPreferences = planketBoxPreferences;
        this.callbackScheme = context.getString(R.string.callback_scheme).concat("://oauth?");

    }

    @Override
    public Single<String> getRequestToken() {
        clearOauthSignature();
        return flickrOauthService.getRequestToken(callbackScheme).map(new Func1<ResponseBody, String>() {
            @Override
            public String call(ResponseBody response) {
                try {
                    String responseBody = response.string();
                    Timber.d(responseBody);
                    requestToken = OAuthTokenExtractor.extractRequestToken(responseBody);
                    return requestToken.getToken();
                } catch (IOException e) {
                    Timber.e(e.getLocalizedMessage());
                    throw Exceptions.propagate(e);
                }
            }
        });
    }

    @Override
    public Single<String> getAccessToken(String oauthToken, String oauthVerifier) {
        okHttpOAuthConsumer.setTokenWithSecret(requestToken.getToken(),requestToken.getTokenSecret());
        return flickrOauthService.getAccessToken(oauthToken,oauthVerifier).map(new Func1<ResponseBody, String>() {
            @Override
            public String call(ResponseBody response) {
                try {
                    String responseBody = response.string();
                    Timber.d(responseBody);
                    final OAuthToken accessToken = OAuthTokenExtractor.extractAccessToken(responseBody);
                    planketBoxPreferences.putAccessToken(accessToken);
                    okHttpOAuthConsumer.setTokenWithSecret(accessToken.getToken(),accessToken.getTokenSecret());
                    return accessToken.getUserId();
                } catch (IOException e) {
                    Timber.e(e.getLocalizedMessage());
                    throw Exceptions.propagate(e);
                }
            }
        });
    }

    private void clearOauthSignature(){
        okHttpOAuthConsumer.setTokenWithSecret("","");
    }
} public class OAuthToken {

    private String token;
    private String tokenSecret;
    private String fullName;
    private String userName;
    private String userId;


    public static class Builder {

        private final String token;
        private final String tokenSecret;

        private String fullName = "";
        private String userName = "";
        private String userId = "";

        public Builder(String token, String tokenSecret) {
            this.token = token;
            this.tokenSecret = tokenSecret;
        }

        public Builder fullName(String value) {
            fullName = value;
            return this;
        }

        public Builder userName(String value) {
            userName = value;
            return this;
        }

        public Builder userId(String value) {
            userId = value;
            return this;
        }

        public OAuthToken build() {
            return new OAuthToken(this);
        }
    }

    private OAuthToken(Builder builder) {
        token = builder.token;
        tokenSecret = builder.tokenSecret;
        fullName = builder.fullName;
        userName = builder.userName;
        userId = builder.userId;
    }

    public String getToken() {
        return token;
    }

    public String getTokenSecret() {
        return tokenSecret;
    }

    public String getFullName() {
        return fullName;
    }

    public String getUserName() {
        return userName;
    }

    public String getUserId() {
        return userId;
    }
} public class PlanketBoxPreferences implements UserHelper {


    public static final String PREF_FILE_NAME = "planket-preferences";
    private static final String PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN";
    private static final String PREF_KEY_USER_PROFILE_PIC= "PREF_KEY_USER_PROFILE_PIC";

    private final SharedPreferences sharedPreferences;
    private Gson gson;

    public PlanketBoxPreferences(Context context, Gson gson) {
        this.sharedPreferences = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
        this.gson = gson;
    }

    public void clearAll() {
        sharedPreferences.edit().clear().apply();
    }

    public void putAccessToken(OAuthToken oAuthToken) {
        sharedPreferences.edit().putString(PREF_KEY_ACCESS_TOKEN, gson.toJson(oAuthToken)).apply();
    }

    public void putUserProfilePicUrl(String userProfilePicUrl){
        sharedPreferences.edit().putString(PREF_KEY_USER_PROFILE_PIC, userProfilePicUrl).apply();
    }


    @Nullable
    public OAuthToken getAccessToken() {
        String token = sharedPreferences.getString(PREF_KEY_ACCESS_TOKEN, null);
        if (token != null) {
            return gson.fromJson(token, OAuthToken.class);
        }
        return null;
    }

    @Nullable
    public String getUserProfilePicUrl() {
        return sharedPreferences.getString(PREF_KEY_USER_PROFILE_PIC, null);
    }

    @Override
    public boolean isUserLoggedIn() {
        if (getAccessToken()!=null) {
            return true;
        }
        return false;
    }

    @Override
    public void logoutUser() {
        clearAll();
    }

    @Override
    public String getUserName() {
        return getAccessToken().getUserName();
    }

    @Override
    public String getFullName() {
        return getAccessToken().getFullName();
    }

    @Override
    public String getUserId() {
        return getAccessToken().getUserId();
    }


} public interface UserHelper {

    boolean isUserLoggedIn();
    void logoutUser();
    String getUserName();
    String getFullName();
    String getUserId();
    String getUserProfilePicUrl();

} public interface FlickrRepository {

    Single<PhotosContainer> getInterestingness(int page);

    Single<PhotosContainer> getPhotosWithTag(@NonNull String tag, int page);

    Single<PhotosContainer> getPhotosWithText(@NonNull String text, int page);

    Single<PhotosContainer> getPhotosForUser(@NonNull String userId, int page);

    Single<PhotosContainer> getFavesForUser(@NonNull String userId, int page);

    Single<UserShowcase> getShowcases(@NonNull String userId, int count);

    Single<FavesContainer> getFaves(@NonNull String photoId, int page);

    Single<CommentsContainer> getComments(@NonNull String photoId, int page);

    Single<FlickrUser> getUserInfo(@NonNull String userId);

    Single<StatusResponse> addPhotoFavorite(@NonNull String photoId);

    Single<StatusResponse> removePhotoFavorite(@NonNull String photoId);

    Observable<SearchPhotosResponse> getAllFaves(@NonNull String userId);

    boolean isPhotoFavorite(@NonNull String photoId);
    void saveUserFavPhotos(@NonNull String userId, @NonNull ArrayList<PhotoItem> photoItems);
    void loadUserFaves();
    void saveUserFavPhoto(@NonNull String photoId);
    void deleteUserFavPhoto(@NonNull String photoId);

} public class OauthPresenter<T extends OauthView> extends BasePresenter<T> {

    private OAuthManager oAuthManager;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public OauthPresenter(OAuthManager oAuthManager, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.oAuthManager = oAuthManager;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }

    public void requestFlickrLogin() {
        checkViewAttached();
        getView().setLoading(true);
        addSubscription(
                oAuthManager.getRequestToken()
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<String>() {
                    @Override
                    public void onSuccess(String requestToken) {
                        String authUrl = OAuthUtil.buildAuthenticationUrl(requestToken);
                        getView().openFlickrLoginScreen(authUrl);
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().setLoading(false);
                        getView().showErrorRequestingFlickrLogin(error.getLocalizedMessage());
                    }
                }));
    }

    public void loginFlickr(String oauthToken, String oauthVerifier) {
        checkViewAttached();
        getView().setLoading(true);
        addSubscription(oAuthManager.getAccessToken(oauthToken,oauthVerifier).observeOn(mainScheduler)
                .subscribeOn(ioScheduler).subscribe(new SingleSubscriber<String>() {
                    @Override
                    public void onSuccess(String userId) {
                        getView().setLoading(false);
                        getView().showFlickrLoginSuccessful(userId);
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().setLoading(false);
                        getView().showErrorLoginFlickr(error.getLocalizedMessage());
                    }
                }));
    }
} public class EgoPresenter extends BasePresenter<EgoView> {

    private FlickrRepository flickrRepository;
    private PaginatedDataManager paginatedDataManager;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public EgoPresenter(FlickrRepository flickrRepository, PaginatedDataManager paginatedDataManager, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.flickrRepository = flickrRepository;
        this.paginatedDataManager = paginatedDataManager;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }

    public void loadComments(String photoId) {
        checkViewAttached();
        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getComments(photoId, nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<CommentsContainer>() {
                    @Override
                    public void onSuccess(CommentsContainer commentsContainer) {
                        paginatedDataManager.setTotalPages(commentsContainer.getTotalPages());
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                        ArrayList<Comment> comments = commentsContainer.getComments();
                        if (comments!=null && comments.size()>0) {
                            getView().showEgo(comments);
                        }else{
                            getView().showNoEgo();
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showErrorScreen(!paginatedDataManager.isLoadingFirstPage() );
                    }
                }));
    }

    public void loadFaves(String photoId) {
        checkViewAttached();
        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getFaves(photoId, nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<FavesContainer>() {
                    @Override
                    public void onSuccess(FavesContainer favesContainer) {
                        paginatedDataManager.setTotalPages(favesContainer.getTotalPages());
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                        ArrayList<Fave> faves = favesContainer.getFaves();
                        if (faves!=null && faves.size()>0) {
                            getView().showEgo(faves);
                        }else{
                            getView().showNoEgo();
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showErrorScreen(!paginatedDataManager.isLoadingFirstPage() );
                    }
                }));
    }
} public class MainPresenter extends BasePresenter<MainView> {

    private FlickrRepository flickrRepository;
    private PlanketBoxPreferences planketBoxPreferences;
    private RxEventBus rxEventBus;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public MainPresenter(FlickrRepository flickrRepository, PlanketBoxPreferences planketBoxPreferences, RxEventBus rxEventBus, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.flickrRepository = flickrRepository;
        this.planketBoxPreferences = planketBoxPreferences;
        this.rxEventBus = rxEventBus;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }

    @Override
    public void attachView(@NonNull MainView mvpView) {
        super.attachView(mvpView);
        addSubscription(rxEventBus.filteredObservable(FlickrUserLoggedInEvent.class)
                .observeOn(mainScheduler)
                .subscribe(new Action1<FlickrUserLoggedInEvent>() {
                    @Override
                    public void call(FlickrUserLoggedInEvent event) {

                                getView().updateUserViews();
                                getUserInfo(event.getUserId());
                                loadUserFavesFromDB();

                    }
                }));
    }

    public void getUserInfo(String userId) {
        checkViewAttached();
        addSubscription(
                flickrRepository.getUserInfo(userId)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<FlickrUser>() {
                    @Override
                    public void onSuccess(FlickrUser flickrUser) {
                        if (flickrUser!=null) {
                            String userProfilePicUrl = flickrUser.getUserProfilePicUrl();
                            planketBoxPreferences.putUserProfilePicUrl(userProfilePicUrl);
                            getView().updateUserProfilePic(userProfilePicUrl);
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        Timber.e(error.getLocalizedMessage());
                    }
                }));
    }

    public void loadUserFavesFromDB() {
        if (planketBoxPreferences.isUserLoggedIn()) {
            flickrRepository.loadUserFaves();
        }
    }

} public class PhotoDetailPresenter extends BasePresenter<PhotoDetailView> {

    private FlickrRepository flickrRepository;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public PhotoDetailPresenter(FlickrRepository flickrRepository, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.flickrRepository = flickrRepository;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }

    public void setPhotoFavorite(final String photoId) {
        checkViewAttached();
        addSubscription(
                flickrRepository.addPhotoFavorite(photoId)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<StatusResponse>() {
                    @Override
                    public void onSuccess(StatusResponse statusResponse) {
                        if (statusResponse.getStatus().equalsIgnoreCase(StatusResponse.STATE_OK) || statusResponse.getCode() == StatusResponse.ERROR_CODE_ALREADY_FAV) {
                            flickrRepository.saveUserFavPhoto(photoId);
                            getView().showPhotoFavoriteSuccessful();
                        }else if (statusResponse.getCode() == StatusResponse.ERROR_CODE_NO_PERMISSIONS){
                            getView().showPhotoFavoriteError("Please log in to Flickr first.");
                        }else{
                            getView().showPhotoFavoriteError("There was an unknown error.");
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showPhotoFavoriteError(error.getLocalizedMessage());
                    }
                }));
    }

    public void removePhotoFavorite(final String photoId) {
        checkViewAttached();
        addSubscription(
                flickrRepository.removePhotoFavorite(photoId)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<StatusResponse>() {
                    @Override
                    public void onSuccess(StatusResponse statusResponse) {
                        if (statusResponse.getStatus().equalsIgnoreCase(StatusResponse.STATE_OK) || statusResponse.getCode() == StatusResponse.ERROR_CODE_PHOTO_NOT_FAV) {
                            flickrRepository.deleteUserFavPhoto(photoId);
                            getView().showRemoveFavoriteSuccessful();
                        }else if (statusResponse.getCode() == StatusResponse.ERROR_CODE_NO_PERMISSIONS){
                            getView().showRemoveFavoriteError("Please log in to Flickr first.");
                        }else{
                            getView().showRemoveFavoriteError("There was an unknown error.");
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showRemoveFavoriteError(error.getLocalizedMessage());
                    }
                }));
    }

    public void checkIfThisPhotoIsFav(String photoId) {
        checkViewAttached();
        getView().setFavButton(flickrRepository.isPhotoFavorite(photoId));
    }
} public class ProfilePresenter extends BasePresenter<ProfileView> {

    private FlickrRepository flickrRepository;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public ProfilePresenter(FlickrRepository flickrRepository, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.flickrRepository = flickrRepository;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }

    public void getUserInfo(String userId) {
        checkViewAttached();
        addSubscription(
                flickrRepository.getUserInfo(userId)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<FlickrUser>() {
                    @Override
                    public void onSuccess(FlickrUser flickrUser) {
                        if (flickrUser!=null){
                            getView().showUserDetails(flickrUser);
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        // TODO: 21/08/16
                    }
                }));
    }

    public void getShowcases(final String userId, int count) {
        checkViewAttached();
        getView().showLoading(true);
        addSubscription(
                flickrRepository.getShowcases(userId, count)
                        .observeOn(mainScheduler)
                        .subscribeOn(ioScheduler)
                        .subscribe(new SingleSubscriber<UserShowcase>() {
                            @Override
                            public void onSuccess(UserShowcase userShowcase) {
                                // Photos
                                boolean isPhotosEmpty = true;
                                ArrayList<PhotoItem> userPhotos = userShowcase.getUserPhotosContainer().getPhotoItems();
                                if (userPhotos!=null && userPhotos.size()>0) {
                                    getView().showUserPhotos(userPhotos, userShowcase.getUserPhotosContainer().getTotal());
                                    isPhotosEmpty = false;
                                }
                                // Faves
                                boolean isFavesEmpty = true;
                                ArrayList<PhotoItem> userFaves = userShowcase.getUserFavesContainer().getPhotoItems();
                                if (userFaves!=null && userFaves.size()>0) {
                                    flickrRepository.saveUserFavPhotos(userId, userFaves);
                                    getView().showUserFaves(userFaves, userShowcase.getUserFavesContainer().getTotal());
                                    isFavesEmpty = false;
                                }

                                if (isPhotosEmpty && isFavesEmpty){
                                    getView().showNoItems();
                                }

                                getView().showLoading(false);
                            }

                            @Override
                            public void onError(Throwable error) {
                                getView().showLoading(false);
                                getView().showErrorScreen();
                            }
                        }));
    }

} public class SearchPresenter extends BasePresenter<SearchView>{

    private FlickrRepository flickrRepository;
    private PaginatedDataManager paginatedDataManager;
    private Scheduler mainScheduler;
    private Scheduler ioScheduler;

    public SearchPresenter(FlickrRepository flickrRepository, PaginatedDataManager paginatedDataManager, Scheduler mainScheduler, Scheduler ioScheduler) {
        this.flickrRepository = flickrRepository;
        this.paginatedDataManager = paginatedDataManager;
        this.mainScheduler = mainScheduler;
        this.ioScheduler = ioScheduler;
    }


    public void loadInterestingness() {
        checkViewAttached();

        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getInterestingness(nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<PhotosContainer>() {
                    @Override
                    public void onSuccess(PhotosContainer photosContainer) {
                        paginatedDataManager.setTotalPages(photosContainer.getTotalPages());
                        ArrayList<PhotoItem> photoItems = photosContainer.getPhotoItems();
                        if (photoItems!=null && photoItems.size()>0) {
                            getView().showPhotoItems(photoItems);
                        }else{
                            getView().showNoItems(!paginatedDataManager.isLoadingFirstPage());
                        }
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showSearchError(!paginatedDataManager.isLoadingFirstPage());
                    }
                }));

    }

    public void searchByTag(final String tag) {
        checkViewAttached();

        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getPhotosWithTag(tag, nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<PhotosContainer>() {
                    @Override
                    public void onSuccess(PhotosContainer photosContainer) {
                        paginatedDataManager.setTotalPages(photosContainer.getTotalPages());
                        ArrayList<PhotoItem> photoItems = photosContainer.getPhotoItems();
                        if (photoItems!=null && photoItems.size()>0) {
                            getView().showPhotoItems(photoItems);
                        }else{
                            getView().showNoItems(!paginatedDataManager.isLoadingFirstPage());
                        }
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showSearchError(!paginatedDataManager.isLoadingFirstPage() );
                    }
                }));
    }

    public void searchByText(final String text) {
        checkViewAttached();

        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getPhotosWithText(text, nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<PhotosContainer>() {
                    @Override
                    public void onSuccess(PhotosContainer photosContainer) {
                        paginatedDataManager.setTotalPages(photosContainer.getTotalPages());
                        ArrayList<PhotoItem> photoItems = photosContainer.getPhotoItems();
                        if (photoItems!=null && photoItems.size()>0) {
                            getView().showPhotoItems(photoItems);
                        }else{
                            getView().showNoItems(!paginatedDataManager.isLoadingFirstPage());
                        }
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showSearchError(!paginatedDataManager.isLoadingFirstPage() );
                    }
                }));
    }

    public void searchByUserId(String userId) {
        checkViewAttached();

        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getPhotosForUser(userId, nextPage)
                .observeOn(mainScheduler)
                .subscribeOn(ioScheduler)
                .subscribe(new SingleSubscriber<PhotosContainer>() {
                    @Override
                    public void onSuccess(PhotosContainer photosContainer) {
                        paginatedDataManager.setTotalPages(photosContainer.getTotalPages());
                        ArrayList<PhotoItem> photoItems = photosContainer.getPhotoItems();
                        if (photoItems!=null && photoItems.size()>0) {
                            getView().showPhotoItems(photoItems);
                        }else{
                            getView().showNoItems(!paginatedDataManager.isLoadingFirstPage());
                        }
                        getView().showLoading(false);
                        paginatedDataManager.addPageLoaded();
                    }

                    @Override
                    public void onError(Throwable error) {
                        getView().showLoading(false);
                        getView().showSearchError(!paginatedDataManager.isLoadingFirstPage());
                    }
                }));
    }

    public void searchFavsByUserId(final String userId) {
        checkViewAttached();

        if (paginatedDataManager.isLoadingFirstPage()) {
            getView().showLoading(true);
        }

        if(paginatedDataManager.areTotalPagesLoaded()){
            getView().showNoMorePagesToLoad();
            return;
        }

        int nextPage = paginatedDataManager.getNextPageToLoad();

        addSubscription(
                flickrRepository.getFavesForUser(userId, nextPage)
                        .observeOn(mainScheduler)
                        .subscribeOn(ioScheduler)
                        .subscribe(new SingleSubscriber<PhotosContainer>() {
                            @Override
                            public void onSuccess(PhotosContainer photosContainer) {
                                flickrRepository.saveUserFavPhotos(userId, photosContainer.getPhotoItems());
                                paginatedDataManager.setTotalPages(photosContainer.getTotalPages());
                                ArrayList<PhotoItem> photoItems = photosContainer.getPhotoItems();
                                if (photoItems!=null && photoItems.size()>0) {
                                    getView().showPhotoItems(photoItems);
                                }else{
                                    getView().showNoItems(!paginatedDataManager.isLoadingFirstPage());
                                }
                                getView().showLoading(false);
                                paginatedDataManager.addPageLoaded();
                            }

                            @Override
                            public void onError(Throwable error) {
                                getView().showLoading(false);
                                getView().showSearchError(!paginatedDataManager.isLoadingFirstPage() );
                            }
                        }));

    }
}
import android.app.Application;
import android.content.Context;
import android.net.ConnectivityManager;
import com.fomdeveloper.planket.BuildConfig;
import com.fomdeveloper.planket.NetworkManager;
import com.fomdeveloper.planket.bus.RxEventBus;
import com.fomdeveloper.planket.data.PlanketDatabase;
import com.fomdeveloper.planket.data.PaginatedDataManager;
import com.fomdeveloper.planket.data.api.FlickrOauthService;
import com.fomdeveloper.planket.data.api.FlickrService;
import com.fomdeveloper.planket.data.api.oauth.OAuthManager;
import com.fomdeveloper.planket.data.api.oauth.OAuthManagerImpl;
import com.fomdeveloper.planket.data.api.oauth.OAuthToken;
import com.fomdeveloper.planket.data.prefs.PlanketBoxPreferences;
import com.fomdeveloper.planket.data.prefs.UserHelper;
import com.fomdeveloper.planket.data.repository.FlickrRepository;
import com.fomdeveloper.planket.ui.presentation.base.oauth.OauthPresenter;
import com.fomdeveloper.planket.ui.presentation.ego.EgoPresenter;
import com.fomdeveloper.planket.ui.presentation.main.MainPresenter;
import com.fomdeveloper.planket.ui.presentation.photodetail.PhotoDetailPresenter;
import com.fomdeveloper.planket.ui.presentation.profile.ProfilePresenter;
import com.fomdeveloper.planket.ui.presentation.searchphotos.SearchPresenter;
import com.google.gson.Gson;
import com.squareup.picasso.Picasso;
import org.mockito.Mockito;
import java.io.IOException;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Scheduler;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer;
import se.akerfeldt.okhttp.signpost.SigningInterceptor;
package com.fomdeveloper.planket.injection;








/**
 * Created by Fernando on 24/12/2016.
 */

@Module
public class MockAppModule {


    /************* MOCKS *************/

    @Provides @Singleton
    public UserHelper provideUserHelper(){
        return Mockito.mock(PlanketBoxPreferences.class);
    }

    @Provides @Singleton
    public FlickrRepository provideFlickrRepository(){
        return Mockito.mock(FlickrRepository.class);
    }

    @Provides @Singleton
    public NetworkManager provideNetworkManager(){
        return Mockito.mock(NetworkManager.class);
    }

    /**************************/

    private Application application;

    public MockAppModule(Application application) {
        this.application = application;
    }

    @Provides  @Singleton
    public Context provideContext(){
        return this.application;
    }

    @Provides  @Singleton
    public Gson provideGson(){
        return new Gson();
    }

    @Provides @Singleton
    public ConnectivityManager provideConnectivityManager(){
        return (ConnectivityManager) application.getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    @Provides @Singleton
    public PlanketBoxPreferences providePlanketPreferences(Context context, Gson gson){
        return new PlanketBoxPreferences(context,gson);
    }

    @Provides @Singleton
    public PlanketDatabase providePlanketDatabase(Context context){
        return new PlanketDatabase(context);
    }

    @Provides @Singleton @Named("main_thread")
    public Scheduler provideMainScheduler(){
        return AndroidSchedulers.mainThread();
    }

    @Provides @Singleton @Named("io_thread")
    public Scheduler provideIOScheduler(){
        return Schedulers.io();
    }

    @Provides @Singleton
    public RxEventBus provideRxBus(){
        return new RxEventBus();
    }

    @Provides @Singleton
    public Picasso providePicasso(Context context){
        return Picasso.with(context);
    }

    @Provides @Named("non_oauth") @Singleton
    public OkHttpClient provideOkHttpClient(OkHttpOAuthConsumer okHttpOAuthConsumer, PlanketBoxPreferences planketBoxPreferences){

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel( BuildConfig.DEBUG? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);

        Interceptor paramInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                HttpUrl url = request.url().newBuilder()
                        .addQueryParameter(FlickrService.PARAM_API_KEY, BuildConfig.FLICKR_API_KEY)
                        .addQueryParameter(FlickrService.PARAM_FORMAT,"json")
                        .addQueryParameter(FlickrService.PARAM_JSONCALLBACK,"1")
                        .build();

                request = request.newBuilder().url(url).build();

                return chain.proceed(request);
            }
        };

        OkHttpClient.Builder okHttpClientBuilder =  new OkHttpClient.Builder()
                .addInterceptor(paramInterceptor)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new SigningInterceptor(okHttpOAuthConsumer));

        if (planketBoxPreferences.getAccessToken()!=null){
            OAuthToken oAuthToken = planketBoxPreferences.getAccessToken();
            okHttpOAuthConsumer.setTokenWithSecret(oAuthToken.getToken(),oAuthToken.getTokenSecret());
        }

        return okHttpClientBuilder.build();

    }

    @Provides @Singleton
    public OkHttpOAuthConsumer provideOkHttpOAuthConsumer(){
        return new OkHttpOAuthConsumer(BuildConfig.FLICKR_API_KEY, BuildConfig.FLICKR_CONSUMER_SECRET);
    }

    @Provides @Named("oauth") @Singleton
    public OkHttpClient provideOauthOkHttpClient(OkHttpOAuthConsumer okHttpOAuthConsumer){

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel( BuildConfig.DEBUG? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);

        return new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new SigningInterceptor(okHttpOAuthConsumer))
                .build();

    }

    @Provides @Named("non_oauth") @Singleton
    public Retrofit provideRetrofit(@Named("non_oauth") OkHttpClient okHttpClient){
        return new Retrofit.Builder()
                .baseUrl( FlickrService.ENDPOINT )
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(okHttpClient)
                .build();
    }

    @Provides @Named("oauth") @Singleton
    public Retrofit provideOauthRetrofit(@Named("oauth") OkHttpClient okHttpClient){
        return new Retrofit.Builder()
                .baseUrl( FlickrOauthService.ENDPOINT )
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(okHttpClient)
                .build();
    }

    @Provides @Singleton
    public FlickrService provideFlickrService(@Named("non_oauth") Retrofit retrofit){
        return retrofit.create(FlickrService.class);
    }

    @Provides @Singleton
    public FlickrOauthService provideFlickrOauthService(@Named("oauth") Retrofit retrofit){
        return retrofit.create(FlickrOauthService.class);
    }

    @Provides @Singleton
    public OAuthManager provideOAuthManager(FlickrOauthService flickrOauthService,OkHttpOAuthConsumer okHttpOAuthConsumer, PlanketBoxPreferences planketBoxPreferences, Context context){
        return new OAuthManagerImpl(flickrOauthService,okHttpOAuthConsumer, planketBoxPreferences, context);
    }

    @Provides
    public PaginatedDataManager providePaginatedManager(){
        return new PaginatedDataManager();
    }

    @Provides
    public MainPresenter provideMainPresenter(FlickrRepository flickrRepository, PlanketBoxPreferences planketBoxPreferences, RxEventBus rxEventBus, @Named("main_thread") Scheduler mainScheduler, @Named("io_thread") Scheduler ioScheduler){