public class VaporApp extends Application {

    private static String TAG = "VaporApp";

    @Inject
    Bus bus;
    @Inject
    NotificationManager notificationManager;
    @Inject
    RefCountManager refCountManager;

    VaporAppComponent vaporAppComponent;
    UiComponent uiComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        //Bad static initializers.
        SugarContext.init(this);
        Once.initialise(this);
        Hawk.init(this)
        .setEncryptionMethod(HawkBuilder.EncryptionMethod.NO_ENCRYPTION)
                .setStorage(HawkBuilder.newSqliteStorage(this))
                .setLogLevel(LogLevel.FULL)
                .build();


        vaporAppComponent = VaporAppComponent.Initializer.init(this);

        uiComponent = DaggerUiComponent.builder()
                .vaporAppComponent(vaporAppComponent)
                .build();

        vaporAppComponent.inject(this);

        registerBus();

        initStetho();

        if (BuildConfig.DEBUG) {
            //When debugging logs will go through the Android logger
            Timber.plant(new Timber.DebugTree());

            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectCustomSlowCalls()
            .detectDiskReads()
            .detectDiskWrites()
            .penaltyLog()
            .build());

            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectActivityLeaks()
            .penaltyLog()
            .build());
        }

        Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this));
    }

    private void registerBus() {
        bus.register(this);
    }

    private void initStetho() {
        Stetho.initialize(
                Stetho.newInitializerBuilder(this)
                        .enableDumpapp(
                                Stetho.defaultDumperPluginsProvider(this))
                        .enableWebKitInspector(
                                Stetho.defaultInspectorModulesProvider(this))
                        .build());
    }


    public VaporApp() {
    }

    public static UiComponent uiComponent(Context context) {
        return ((VaporApp) context.getApplicationContext()).uiComponent;
    }

    public static VaporAppComponent appComponent(Context context) {
        return ((VaporApp) context.getApplicationContext()).vaporAppComponent;
    }

    public static void recreateUiComponent(Context context) {
        ((VaporApp) context.getApplicationContext()).uiComponent = DaggerUiComponent.builder()
                .vaporAppComponent(((VaporApp) context.getApplicationContext()).vaporAppComponent)
                .build();
    }

    public static void recreateVaporComponent(Context context) {
        ((VaporApp) context.getApplicationContext()).vaporAppComponent =
                VaporAppComponent.Initializer.init((VaporApp)context.getApplicationContext());
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        Glide.get(this).clearMemory();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        Glide.get(this).trimMemory(level);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        SugarContext.terminate();
        bus.unregister(this);

        for (Integer notificationId : refCountManager.getNotificationIds()) {
            notificationManager.cancel(notificationId);
            Timber.i("CLEAN UP from onTerminate with id=%s", notificationId);
        }
    }

    @Subscribe
    public void onLogoutEvent(LogoutEvent event) {
        // Clear the cache of the Http client before we create a new one.
        try {
            vaporAppComponent.client().getCache().evictAll();
        } catch (IOException e) {
            e.printStackTrace();
        }
        VaporApp.recreateVaporComponent(this);
        VaporApp.recreateUiComponent(this);
        Glide.get(VaporApp.this).clearMemory();
        Single.just(new Object()).subscribeOn(Schedulers.io()).subscribe(new Action1<Object>() {
            @Override
            public void call(Object o) {
                Glide.get(VaporApp.this).clearDiskCache();
            }
        });


        vaporAppComponent.inject(this);
        registerBus();


    }

    /*http://stackoverflow.com/questions/4028742/how-to-clear-a-notification-if-activity-crashes*/
    public class CrashHandler implements Thread.UncaughtExceptionHandler {

        private Thread.UncaughtExceptionHandler defaultUEH;
        private NotificationManager notificationManager;

        public CrashHandler(Context context) {
            this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
            notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        public void uncaughtException(Thread t, Throwable e) {
            for (Integer notificationId : refCountManager.getNotificationIds()) {
                notificationManager.cancel(notificationId);
                Timber.i("CLEAN UP from uncaughtException with id=%s", notificationId);
            }
            notificationManager = null;
            defaultUEH.uncaughtException(t, e);
        }
    }
} public class MarginDecoration extends RecyclerView.ItemDecoration {
    private final int margin;

    public MarginDecoration(Context context) {
        margin = context.getResources().getDimensionPixelSize(R.dimen.item_margin);
    }


    @Override
    public void getItemOffsets(
            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.set(margin, margin, margin, margin);
    }
} public class DeleteEvent extends DataSetUpdateEvent {
    public DeleteEvent(CloudAppItem item) {
        super(item);
    }
} public class RenameEvent extends DataSetUpdateEvent {
    public RenameEvent(CloudAppItem item) {
        super(item);
    }
} public class UploadEvent {
    private Uri uri;
    private CloudAppItem item;

    public UploadEvent(Uri uri, CloudAppItem item) {
        this.uri = uri;
        this.item = item;
    }

    public UploadEvent() {
    }

    @Nullable
    public Uri getUri() {
        return uri;
    }

    public CloudAppItem getItem() {
        return item;
    }
} public class DataManager {

    private final CloudAppService cloudAppService;
    private final Bus bus;
    private final UserManager userManager;
    private final int SERVER_ITEM_LIMIT = 40;
    private final int MAX_ITEM_LIMIT = 1500;
    private final OkHttpClient client;
    private boolean isSyncingAllItems;

    public final static String SYNC_ALL_ITEMS = DataManager.class.getPackage().getName() + ".SYNC_ALL_ITEMS";

    public DataManager(CloudAppService cloudAppService, UserManager userManager, Bus bus, OkHttpClient client) {
        this.cloudAppService = cloudAppService;
        this.bus = bus;
        this.userManager = userManager;
        this.client = client;
        bus.register(this);
    }

    public void syncAllItems(final boolean notify) {
        if (!isSyncingAllItems) {
            cloudAppService.getAccountStats()
                    .flatMap(new Func1<AccountStatsModel, Observable<List<CloudAppItem>>>() {
                        @Override
                        public Observable<List<CloudAppItem>> call(final AccountStatsModel accountStatsModel) {
                            return Observable.create(new Observable.OnSubscribe<Integer>() {
                                @Override
                                public void call(Subscriber<? super Integer> subscriber) {
                                    if (!subscriber.isUnsubscribed()) {
                                        for (int i = 1; i <= Math.ceil((double) accountStatsModel.getItems() / (double) SERVER_ITEM_LIMIT) + 1
                                                && i < Math.ceil(MAX_ITEM_LIMIT / SERVER_ITEM_LIMIT); i++) {
                                            subscriber.onNext(i);
                                        }
                                        subscriber.onCompleted();
                                    }
                                }
                            })
                                    .subscribeOn(Schedulers.io())
                                    .flatMap(new Func1<Integer, Observable<List<CloudAppItem>>>() {
                                        @Override
                                        public Observable<List<CloudAppItem>> call(Integer integer) {
                                            return getListFromServer(makeListParams(integer, ItemType.ALL, false, SERVER_ITEM_LIMIT));
                                        }
                                    }).subscribeOn(Schedulers.io())
                                    .flatMap(new Func1<List<CloudAppItem>, Observable<List<CloudAppItem>>>() {
                                        @Override
                                        public Observable<List<CloudAppItem>> call(List<CloudAppItem> cloudAppItems) {
                                            SugarRecord.updateInTx(cloudAppItems);
                                            return Observable.just(cloudAppItems);
                                        }
                                    });

                        }
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .doOnTerminate(new Action0() {
                        @Override
                        public void call() {
                            isSyncingAllItems = false;
                            if (notify) {
                                bus.post(new DatabaseUpdateEvent());
                            }
                        }
                    })
                    .doOnSubscribe(new Action0() {
                        @Override
                        public void call() {
                            isSyncingAllItems = true;
                        }
                    })
                    .subscribe(new Action1<List<CloudAppItem>>() {
                        @Override
                        public void call(List<CloudAppItem> cloudAppItems) {
                            Timber.i("Synced all items. Size: %s", cloudAppItems.size());
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            Timber.e(throwable, "Error syncing all items");
                        }
                    });

            getTrashItems(ItemType.ALL, true, new DataCursor())
                    .subscribe(new Action1<List<CloudAppItem>>() {
                        @Override
                        public void call(List<CloudAppItem> cloudAppItems) {
                            Timber.i("Synced all deleted items. Size: %s", cloudAppItems.size());
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            Timber.e(throwable, "Error syncing all deleted items");
                        }
                    });
        }
    }

    @NonNull
    private Func1<List<CloudAppItem>, Observable<CloudAppItem>> reduceList() {
        return new Func1<List<CloudAppItem>, Observable<CloudAppItem>>() {
            @Override
            public Observable<CloudAppItem> call(List<CloudAppItem> cloudAppItems) {
                return Observable.from(cloudAppItems);
            }
        };
    }

    @NonNull
    private Func1<CloudAppItem, CloudAppItem> saveToDb() {
        return new Func1<CloudAppItem, CloudAppItem>() {
            @Override
            public CloudAppItem call(CloudAppItem cloudAppItem) {
                SugarRecord.updateInTx(cloudAppItem);
                Timber.i("Inserting or update item: %s", cloudAppItem.getItemId());
                return cloudAppItem;
            }
        };
    }

    private void deleteLocalItem(CloudAppItem cloudAppItem) {
        SugarRecord.delete(cloudAppItem);
        Timber.i("Deleting item: %s", cloudAppItem.getItemId());
    }

    public void purgeDeletedItems() {
        long expired = DateTime.now().minusDays(7).getMillis();
        Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                "DELETE FROM CLOUD_APP_ITEM WHERE deleted_at <> -1 AND deleted_at < ?",
                String.valueOf(expired)))
                .subscribe(new Action1<List<CloudAppItem>>() {
                    @Override
                    public void call(List<CloudAppItem> cloudAppItems) {
                        Timber.i("Deleting items: %s", cloudAppItems.size());
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        Timber.e(throwable, "Error deleting stale items.");
                    }
                });
    }

    public Observable<CloudAppItem> deleteCloudItem(final CloudAppItem cloudAppItem) {
        return cloudAppService.deleteItem(String.valueOf(cloudAppItem.getItemId()))
                .map(convertItemModel())
                .doOnNext(new Action1<CloudAppItem>() {
                    @Override
                    public void call(CloudAppItem item) {
                        deleteLocalItem(cloudAppItem);
                    }
                });
    }

    public Observable<CloudAppItem> renameCloudItem(final CloudAppItem cloudAppItem, String newName) {
        CloudAppJsonItem jsonItem = new CloudAppJsonItem();
        CloudAppJsonItem.Item item = jsonItem.item = new CloudAppJsonItem.Item();
        item.name = newName;

        return cloudAppService.renameItem(String.valueOf(cloudAppItem.getItemId()), jsonItem)
                .map(convertItemModel())
                .map(saveToDb());
    }

    public Observable<CloudAppItem> bookmarkItem(final String name, String url) {
        CloudAppJsonItem jsonItem = new CloudAppJsonItem();
        CloudAppJsonItem.Item item = jsonItem.item = new CloudAppJsonItem.Item();
        item.name = name;
        item.redirectUrl = url;

        return cloudAppService.bookmarkLink(jsonItem)
                .map(convertItemModel())
                .map(saveToDb())
                .retryWhen(new RxUtils.RetryWithDelay(2, 2000));
    }

    public Observable<CloudAppItem> upload(final CloudAppRequestBody requestBody) {
        return cloudAppService.newUpload(makeQueryMap(requestBody.getFileName(), String.valueOf(requestBody.contentLength())))
                .flatMap(new Func1<UploadModel, Observable<CloudAppItem>>() {
                    @Override
                    public Observable<CloudAppItem> call(final UploadModel uploadModel) {
                        if (uploadModel.getParams() == null) {
                            return Observable.error(new UploadLimitException("Daily upload limit reached"));
                        }
                        if (requestBody.contentLength() > uploadModel.getMax_upload_size()) {
                            return Observable.error(new FileToLargeException("File too large for you current plan"));
                        }
                        return cloudAppService.uploadFile(makeMultipartParams(uploadModel, requestBody))
                                .map(convertItemModel())
                                .map(saveToDb())
                                .retryWhen(new RxUtils.RetryWithDelay(4, 2000));

                    }
                });
    }

    private Map<String, String> makeQueryMap(String name, String size) {
        Map<String, String> params = new LinkedHashMap<>();
        params.put("name", name);
        params.put("file_size", size);
        return params;
    }

    private Observable<List<CloudAppItem>> getAllItems(final ItemType type, final DataCursor cursor) {
        if (!userManager.isLoggedIn()) {
            return Observable.error(new LoginException());
        }
        if (type == ItemType.ALL) {
            return Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE deleted_at = -1 ORDER BY ITEM_ID DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"),
                            String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class, query,
                            String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }});
        }
        return Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
            @Override
            public Observable<List<CloudAppItem>> call() {
                String query = "SELECT * FROM CLOUD_APP_ITEM WHERE ITEM_TYPE = ? AND deleted_at = -1 ORDER BY ITEM_ID DESC LIMIT ? OFFSET ?";
                Timber.d(query.replace("?", "%s"),
                        type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class, query,
                                type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
            }
        });
    }

    public Observable<List<CloudAppItem>> getPopularItems(final ItemType type, boolean refresh, final DataCursor cursor) {
        if (!userManager.isLoggedIn()) {
            return Observable.error(new LoginException());
        }
        final Observable<List<CloudAppItem>> observable;

        if (type == ItemType.ALL) {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE deleted_at = -1 ORDER BY VIEW_COUNTER DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }});
        } else {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE ITEM_TYPE = ? AND deleted_at = -1 ORDER BY VIEW_COUNTER DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }});
        }
        if (refresh) {
            return refreshPage(1, false)
                    .flatMap(new Func1<List<CloudAppItem>, Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call(List<CloudAppItem> cloudAppItems) {
                    return observable;
                }
            });
        }
        return observable.doOnNext(new Action1<List<CloudAppItem>>() {
            @Override
            public void call(List<CloudAppItem> cloudAppItems) {
                cursor.setOffset(cursor.getOffset() + cloudAppItems.size());
            }
        });
    }

    public Observable<List<CloudAppItem>> getFavoriteItems(final ItemType type, boolean refresh, final DataCursor cursor) {
        if (!userManager.isLoggedIn()) {
            return Observable.error(new LoginException());
        }
        final Observable<List<CloudAppItem>> observable;

        if (type == ItemType.ALL) {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE FAVORITE = 1 AND deleted_at = -1 ORDER BY ITEM_ID DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }
            });
        } else {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE ITEM_TYPE = ? AND FAVORITE = 1 AND deleted_at = -1 ORDER BY ITEM_ID, VIEW_COUNTER DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }});
        }
        if (refresh) {
            return refreshPage(1, false).flatMap(new Func1<List<CloudAppItem>, Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call(List<CloudAppItem> cloudAppItems) {
                    return observable;
                }
            });
        }
        return observable.doOnNext(new Action1<List<CloudAppItem>>() {
            @Override
            public void call(List<CloudAppItem> cloudAppItems) {
                cursor.setOffset(cursor.getOffset() + cloudAppItems.size());
            }
        });
    }

    public Observable<List<CloudAppItem>> getTrashItems(final ItemType type, boolean refresh, final DataCursor cursor) {
        if (!userManager.isLoggedIn()) {
            return Observable.error(new LoginException());
        }
        final Observable<List<CloudAppItem>> observable;
        if (type == ItemType.ALL) {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE DELETED_AT <> -1 ORDER BY DELETED_AT DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return  Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }
            });
        } else {
            observable = Observable.defer(new Func0<Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call() {
                    String query = "SELECT * FROM CLOUD_APP_ITEM WHERE ITEM_TYPE = ? AND DELETED_AT <> -1 ORDER BY DELETED_AT DESC LIMIT ? OFFSET ?";
                    Timber.d(query.replace("?", "%s"), type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset()));
                    return Observable.just(SugarRecord.findWithQuery(CloudAppItem.class,
                            query, type.toString().toLowerCase(), String.valueOf(cursor.getLimit()), String.valueOf(cursor.getOffset())));
                }});
        }
        if (refresh) {
            return refreshPage(1, true).flatMap(new Func1<List<CloudAppItem>, Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call(List<CloudAppItem> cloudAppItems) {
                    return observable;
                }
            });
        }
        return observable.doOnNext(new Action1<List<CloudAppItem>>() {
            @Override
            public void call(List<CloudAppItem> cloudAppItems) {
                cursor.setOffset(cursor.getOffset() + cloudAppItems.size());
            }
        });
    }

    public Observable<List<CloudAppItem>> getAllItems(final ItemType type, boolean refresh, final DataCursor cursor) {
        if (!userManager.isLoggedIn()) {
            return Observable.error(new LoginException());
        }
        final Observable<List<CloudAppItem>> observable = getAllItems(type, cursor);
        if (refresh) {
            return refreshPage(1, false).flatMap(new Func1<List<CloudAppItem>, Observable<List<CloudAppItem>>>() {
                @Override
                public Observable<List<CloudAppItem>> call(List<CloudAppItem> cloudAppItems) {
                    return observable;
                }
            });
        }
        return observable.doOnNext(new Action1<List<CloudAppItem>>() {
            @Override
            public void call(List<CloudAppItem> cloudAppItems) {
                cursor.setOffset(cursor.getOffset() + cloudAppItems.size());
            }
        });
    }

    @NonNull
    private Observable<List<CloudAppItem>> refreshPage(int page, boolean deleted) {
        return getListFromServer(makeListParams(page, ItemType.ALL, deleted, SERVER_ITEM_LIMIT))
                .flatMap(reduceList())
                .map(saveToDb())
                .toList();
    }

    private Observable<List<CloudAppItem>> getListFromServer(Map<String, String> options) {
        return cloudAppService.listItems(options)
                .retryWhen(new RxUtils.RetryWithDelay(2, 2000))
                .subscribeOn(Schedulers.io())
                .flatMap(new Func1<List<ItemModel>, Observable<List<CloudAppItem>>>() {
                    @Override
                    public Observable<List<CloudAppItem>> call(List<ItemModel> itemModels) {
                        return Observable.from(itemModels)
                                .map(convertItemModel()).toList();
                    }
                });
    }

    @NonNull
    private Func1<ItemModel, CloudAppItem> convertItemModel() {
        return new Func1<ItemModel, CloudAppItem>() {
            @Override
            public CloudAppItem call(ItemModel itemModel) {
                return new CloudAppItem(itemModel);
            }
        };
    }

    private Map<String, String> makeListParams(int page, ItemType type, boolean deleted, int pageSize) {
        Map<String, String> params = new HashMap<>();
        params.put("page", String.valueOf(page));
        params.put("per_page", String.valueOf(pageSize));
        if (type != ItemType.ALL) {
            params.put("type", type.toString().toLowerCase());
        }
        params.put("deleted", String.valueOf(deleted));

        return params;
    }

    private Map<String, RequestBody> makeMultipartParams(UploadModel uploadModel, CloudAppRequestBody body) {
        String filename = "file\"; filename=\"" + body.getFileName();
        Map<String, RequestBody> params = new LinkedHashMap<>();
        params.put("AWSAccessKeyId", createStringBody(uploadModel.getParams().getAWSAccessKeyId()));
        params.put("key", createStringBody(uploadModel.getParams().getKey()));
        params.put("acl", createStringBody(uploadModel.getParams().getAcl()));
        params.put("success_action_redirect", createStringBody(uploadModel.getParams().getSuccess_action_redirect()));
        params.put("signature", createStringBody(uploadModel.getParams().getSignature()));
        params.put("policy",createStringBody( uploadModel.getParams().getPolicy()));
        params.put(filename, body);
        return params;
    }

    private RequestBody createStringBody(String string) {
        return RequestBody.create(MediaType.parse("text/plain"), string);
    }

    @Subscribe
    public void onLogoutEvent(LogoutEvent event) {
        List<CloudAppItem> cloudAppItems = SugarRecord.listAll(CloudAppItem.class);
        SugarRecord.deleteInTx(cloudAppItems);
        Once.clearAll();
        CloudAppItem.executeQuery("DELETE FROM sqlite_sequence WHERE NAME = 'CLOUD_APP_ITEM'");
        CloudAppItem.executeQuery("VACUUM");
    }

    public boolean isSyncingAllItems() {
        return isSyncingAllItems;
    }

    public static class DataCursor implements Parcelable {
        public final int ITEM_LIST_LIMIT = 100;
        int offset = 0;
        String owner = "Dummy";

        public DataCursor(String owner) {
            this.owner = owner;
        }

        public int getLimit() {
            return ITEM_LIST_LIMIT;
        }

        public int getOffset() {
            return offset;
        }

        public void setOffset(int offset) {
            Timber.i("%s Cursor was %s now %s", owner,  this.offset, offset);
            this.offset = offset;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.offset);
        }

        public DataCursor() {
        }

        protected DataCursor(Parcel in) {
            this.offset = in.readInt();
        }

        public static final Creator<DataCursor> CREATOR = new Creator<DataCursor>() {
            public DataCursor createFromParcel(Parcel source) {
                return new DataCursor(source);
            }

            public DataCursor[] newArray(int size) {
                return new DataCursor[size];
            }
        };
    }
} public class CloudAppItem extends SugarRecord implements Comparable<CloudAppItem>,Parcelable {

    @Unique
    private long itemId;
    private String href;
    private String name;
    private boolean isPrivate;
    private boolean isSubscribed;
    private String contentUrl;
    private String itemType;
    private long viewCounter;
    private String icon;
    private String url;
    private String remoteUrl;
    private String thumbnailUrl;
    private String downloadUrl;
    private String source;
    boolean favorite;
    String ownerId;
    long contentLength;
    private String createdAt;
    private String updatedAt;
    private String deletedAt;
    private String lastViewedAt;

    public CloudAppItem(ItemModel itemModel) {
        this.itemId = itemModel.id;
        this.href = itemModel.href;
        this.name = itemModel.name;
        this.isPrivate = itemModel.isPrivate;
        this.isSubscribed = itemModel.isSubscribed;
        this.contentUrl = itemModel.contentUrl;
        this.itemType = itemModel.itemType;
        this.viewCounter = itemModel.viewCounter;
        this.icon = itemModel.icon;
        this.url = itemModel.url;
        this.remoteUrl = itemModel.remoteUrl;
        this.thumbnailUrl = itemModel.thumbnailUrl;
        this.downloadUrl = itemModel.downloadUrl;
        this.source = itemModel.source;
        this.favorite = itemModel.favorite;
        this.ownerId = itemModel.ownerId;
        this.contentLength = itemModel.contentLength;
        this.createdAt = String.valueOf(formatDate(itemModel.createdAt));
        this.updatedAt = String.valueOf(formatDate(itemModel.updatedAt));
        this.deletedAt = String.valueOf(formatDate(itemModel.deletedAt));
        this.lastViewedAt = String.valueOf(formatDate(itemModel.lastViewedAt));
    }

    public CloudAppItem() {
    }

    public long getItemId() {
        return itemId;
    }

    public String getDownloadUrl() {
        return downloadUrl;
    }

    public String getHref() {
        return href;
    }

    
    public String getName() {
        return name;
    }

    
    public boolean isPrivate() {
        return isPrivate;
    }

    
    public boolean isSubscribed() {
        return isSubscribed;
    }

    public boolean isTrashed() {
        return !deletedAt.equals(String.valueOf(-1));
    }

    public String getUrl() {
        return url == null? remoteUrl : url;
    }

    
    public String getContentUrl() {
        return contentUrl;
    }

    
    public ItemType getItemType() {
        try {
            return ItemType.valueOf(itemType.toUpperCase());
        } catch (IllegalArgumentException e) {
            return ItemType.UNKNOWN;
        }
    }

    
    public long getViewCounter() {
        return viewCounter;
    }

    
    public String getIcon() {
        return icon;
    }

    
    public String getRemoteUrl() {
        return remoteUrl;
    }

    
    public String getSource() {
        return source;
    }

    
    public String getCreatedAt() {
        return createdAt;
    }

    
    public String getUpdatedAt() {
        return updatedAt;
    }

    
    public String getDeletedAt() {
        return deletedAt;
    }

    
    public String getLastViewedAt() {
        return lastViewedAt;
    }

    
    public String getThumbnailUrl() {
        return thumbnailUrl;
    }

    public long getContentLength() {
        return contentLength;
    }

    public String getFormattedCreatedAt(Context context)  {

        return formatDateTime(context, Long.parseLong(createdAt), FORMAT_SHOW_TIME|FORMAT_SHOW_YEAR|FORMAT_SHOW_DATE);
    }

    
    public String getFormattedUpdatedAt()  {
        try {
            return updatedAt ==null?null:DateFormat.getDateInstance()
                    .format(CloudAppUtils.format.parse(updatedAt));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    
    public String getFormattedDeletedAt() {
        try {
            return deletedAt ==null?null:DateFormat.getDateInstance()
                    .format(CloudAppUtils.format.parse(deletedAt));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean isFavorite() {
        return favorite;
    }

    public String getOwnerId() {
        return ownerId;
    }

    @Override
    public int compareTo(CloudAppItem another) {
        CloudAppItem lhs = this;
        if (another != null) {
            if (lhs.equals(another)) return 0;
            if (lhs.getItemId() < another.getItemId()) return 1;
            if (lhs.getItemId() < another.getItemId()) return -1;
        }
        return -1;
    }

    public enum ItemType {
        ALL, DELETED, AUDIO, BOOKMARK, IMAGE, UNKNOWN, VIDEO, ARCHIVE, TEXT
    }

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

    public void setDeletedAt(String deletedAt) {
        this.deletedAt = deletedAt;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CloudAppItem item = (CloudAppItem) o;

        return itemId == item.itemId;

    }

    public int hash() {
        int result = href.hashCode();
        result = 31 * result + createdAt.hashCode();
        result = 31 * result + updatedAt.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "CloudAppItem{" +
                "name='" + name + '\'' +
                ", href='" + href + '\'' +
                ", itemId=" + itemId +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(this.itemId);
        dest.writeString(this.href);
        dest.writeString(this.name);
        dest.writeByte(isPrivate ? (byte) 1 : (byte) 0);
        dest.writeByte(isSubscribed ? (byte) 1 : (byte) 0);
        dest.writeString(this.contentUrl);
        dest.writeString(this.itemType);
        dest.writeLong(this.viewCounter);
        dest.writeString(this.icon);
        dest.writeString(this.url);
        dest.writeString(this.remoteUrl);
        dest.writeString(this.thumbnailUrl);
        dest.writeString(this.downloadUrl);
        dest.writeString(this.source);
        dest.writeByte(favorite ? (byte) 1 : (byte) 0);
        dest.writeString(this.ownerId);
        dest.writeLong(this.contentLength);
        dest.writeString(this.createdAt);
        dest.writeString(this.updatedAt);
        dest.writeString(this.deletedAt);
        dest.writeString(this.lastViewedAt);
    }

    protected CloudAppItem(Parcel in) {
        this.itemId = in.readLong();
        this.href = in.readString();
        this.name = in.readString();
        this.isPrivate = in.readByte() != 0;
        this.isSubscribed = in.readByte() != 0;
        this.contentUrl = in.readString();
        this.itemType = in.readString();
        this.viewCounter = in.readLong();
        this.icon = in.readString();
        this.url = in.readString();
        this.remoteUrl = in.readString();
        this.thumbnailUrl = in.readString();
        this.downloadUrl = in.readString();
        this.source = in.readString();
        this.favorite = in.readByte() != 0;
        this.ownerId = in.readString();
        this.contentLength = in.readLong();
        this.createdAt = in.readString();
        this.updatedAt = in.readString();
        this.deletedAt = in.readString();
        this.lastViewedAt = in.readString();
    }

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

        public CloudAppItem[] newArray(int size) {
            return new CloudAppItem[size];
        }
    };
} public enum ItemType {
    ALL, DELETED, AUDIO, BOOKMARK, IMAGE, UNKNOWN, VIDEO, ARCHIVE, TEXT
} public interface Presenter<V extends View> extends StatefulPresenter {
    V attachView(V view);
    void detachView();
    V getView();
} public interface View {
    enum LayoutType {
        EMPTY, ERROR, LIST, LOADING
    }
} public interface FilesActivityPresenter extends Presenter<FilesActivityView> {
    void loadEmail();
    void setNavContext(FilesActivityPresenterImpl.NavContext navContext);
    FilesActivityPresenterImpl.NavContext getNavContext();
    void refreshClicked();

    enum NavContext {
        ALL("All Recent Files"), POPULAR("Popular"), FAVORITE("Favorites"), TRASH("Trash");

        private final String title;

        NavContext(String title) {
            this.title = title;
        }

        @Override
        public String toString() {
            return title;
        }
    }
} public class ArchivePresenter extends BaseFilesPresenterImpl {

    public ArchivePresenter() {
        itemType = ItemType.ARCHIVE;
    }

    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }

} public class AudioPresenter extends BaseFilesPresenterImpl {
    public AudioPresenter() {
        itemType = ItemType.AUDIO;
    }
    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }
} public class BookmarkPresenter extends BaseFilesPresenterImpl {
    public BookmarkPresenter() {
        itemType = ItemType.BOOKMARK;
    }
    @Inject
    Bus bus;


    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, false, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }
} public interface FilesPresenter<V extends View> extends Presenter<V> {
    void loadData(boolean pullToRefresh, boolean refreshData, boolean useCursor);
    boolean isNotLoading();
    boolean shouldShowEmpty();
} public class ImagePresenter extends BaseFilesPresenterImpl {
    public ImagePresenter() {
        itemType = ItemType.IMAGE;
    }

    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }

} public class RecentPresenter extends BaseFilesPresenterImpl {
    public RecentPresenter() {
        itemType = CloudAppItem.ItemType.ALL;
    }

    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }

} public class TextPresenter extends BaseFilesPresenterImpl {
    public TextPresenter() {
        itemType = ItemType.TEXT;
    }
    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }
} public class UnknownPresenter extends BaseFilesPresenterImpl {
    public UnknownPresenter() {
        itemType = ItemType.UNKNOWN;
    }
    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }
} public class VideoPresenter extends BaseFilesPresenterImpl {
    public VideoPresenter() {
        itemType = ItemType.VIDEO;
    }
    @Inject
    Bus bus;

    @Subscribe
    public void dbUpdate(DatabaseUpdateEvent event) {
        loadData(false, false, true);
    }

    @Subscribe
    public void onUploadEvent(UploadEvent event) {
        loadData(false, true, false);
    }

    @Subscribe
    public void onRefreshEvent(RefreshEvent event) {
        if (getView() != null && getView().isVisibleInPager()) {
            loadData(true, true, false);
        }
    }
    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }
} public class LoginException extends Exception {
    private int code;
    public LoginException() {
    }

    public LoginException(String detailMessage) {
        super(detailMessage);
    }

    public LoginException(String detailMessage, Throwable throwable) {
        super(detailMessage, throwable);
    }

    public void setCode(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
} public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();

    private int previousTotal = 0; // The total number of items in the dataset after the last load
    private boolean loading = true; // True if we are still waiting for the last set of data to load.
    private int visibleThreshold = 20; // The minimum amount of items to have below your current scroll position before loading more.
    int firstVisibleItem, visibleItemCount, totalItemCount;

    private int current_page = 1;

    private GridLayoutManager layoutManager;

    public EndlessRecyclerOnScrollListener(GridLayoutManager linearLayoutManager) {
        this.layoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = layoutManager.getItemCount();
        firstVisibleItem = layoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading && (totalItemCount - visibleItemCount)
                <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached

            // Do something
            current_page++;

            onLoadMore(current_page);

            loading = true;
        }
    }


    public abstract void onLoadMore(int current_page);
}
import android.content.res.Resources;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import com.tevinjeffrey.vapor.R;
import com.tevinjeffrey.vapor.VaporApp;
import com.tevinjeffrey.vapor.customviews.MarginDecoration;
import com.tevinjeffrey.vapor.events.DeleteEvent;
import com.tevinjeffrey.vapor.events.RenameEvent;
import com.tevinjeffrey.vapor.events.UploadEvent;
import com.tevinjeffrey.vapor.okcloudapp.DataManager;
import com.tevinjeffrey.vapor.okcloudapp.model.CloudAppItem;
import com.tevinjeffrey.vapor.okcloudapp.model.CloudAppItem.ItemType;
import com.tevinjeffrey.vapor.ui.base.Presenter;
import com.tevinjeffrey.vapor.ui.base.View;
import com.tevinjeffrey.vapor.ui.files.FilesActivityPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.ArchivePresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.AudioPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.BookmarkPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.FilesPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.ImagePresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.RecentPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.TextPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.UnknownPresenter;
import com.tevinjeffrey.vapor.ui.files.fragments.presenters.VideoPresenter;
import com.tevinjeffrey.vapor.ui.login.LoginException;
import com.tevinjeffrey.vapor.ui.utils.EndlessRecyclerOnScrollListener;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import icepick.State;
import timber.log.Timber;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
package com.tevinjeffrey.vapor.ui.files.fragments;








public class FilesFragment extends MVPFragment implements FilesFragmentView, SwipeRefreshLayout.OnRefreshListener {
    private static final String ITEM_TYPE = "ITEM_TYPE";
    @Bind(R.id.files_recyclerview) RecyclerView mRecyclerView;
    @Bind(R.id.empty_view) LinearLayout mEmptyView;
    @Bind(R.id.progressBar) ProgressBar mProgressBar;
    @Bind(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;

    @Inject Bus bus;

    @State
    FilesFragmentViewState mViewState = new FilesFragmentViewState();

    @State
    DataManager.DataCursor cursor;

    private ItemType mItemType;

    private List<CloudAppItem> mListDataSet;
    private boolean isViewShown;

    public static FilesFragment newInstance(ItemType itemType) {
        FilesFragment fragment = new FilesFragment();
        Bundle bundle = new Bundle();
        bundle.putSerializable(ITEM_TYPE, itemType);
        fragment.setArguments(bundle);
        return fragment;
    }

    public FilesFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setRetainInstance(true);
        super.onCreate(savedInstanceState);
        VaporApp.uiComponent(getParentActivity()).inject(this);
        if (getArguments() != null) {
            mItemType = (ItemType) getArguments().getSerializable(ITEM_TYPE);
        }
    }

    @Override
    public android.view.View onCreateView(LayoutInflater inflater, ViewGroup container,
                                          Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        android.view.View view = inflater.inflate(R.layout.fragment_files_list, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(android.view.View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (mPresenter == null) {
            setPresenter(getTypedPresenter());
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //Attach view to presenter
        mViewState.apply(this, savedInstanceState != null);

        if (getCursor() == null) {
            cursor = new DataManager.DataCursor(getPresenter().getClass().getSimpleName());
        }
        mPresenter.attachView(this);
        if (getPresenter().isNotLoading()) {
            getPresenter().loadData(true, false, true);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);

    }

    @Override
    public void onPause() {
        super.onPause();
        bus.unregister(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    private FilesPresenter getPresenter() {