public class DashboardFragment extends Fragment
        implements SwipeRefreshLayout.OnRefreshListener,
        AvailableCard.AvailableCardListener,
        ReviewsCard.ReviewsCardListener,
        SRSCard.StatusCardListener,
        ProgressCard.ProgressCardListener,
        RecentUnlocksCard.RecentUnlocksCardListener,
        CriticalItemsCard.CriticalItemsCardListener,
        MessageCard.MessageCardListener,
        View.OnClickListener {

    public static final String SYNC_RESULT_SUCCESS = "success";
    public static final String SYNC_RESULT_FAILED = "failed";
    View rootView;
    AppCompatActivity activity;
    boolean isAvailableCardSynced = false;
    boolean isReviewsCardSynced = false;
    boolean isStatusCardSynced = false;
    boolean isProgressCardSynced = false;
    boolean isRecentUnlocksCardSynced = false;
    boolean isCriticalItemsCardSynced = false;
    boolean isAvailableCardSyncedSuccess = false;
    boolean isReviewsCardSyncedSuccess = false;
    boolean isStatusCardSyncedSuccess = false;
    boolean isProgressCardSyncedSuccess = false;
    boolean isRecentUnlocksCardSyncedSuccess = false;
    boolean isCriticalItemsCardSyncedSuccess = false;
    LinearLayout mAvailableHolder;
    CardView mReviewsHolder;
    CardView mProgressHolder;
    LinearLayout mCriticalItemsFragmentHolder;
    LinearLayout mRecentUnlocksFragmentHolder;
    CardView mMessageCardHolder;
    CardView mNotificationsCardHolder;
    CardView mVacationModeCardHolder;
    FrameLayout mVacationModeCard;
    FrameLayout mReviewsCard;
    FrameLayout mProgressCard;
    private Context context;
    private SwipeRefreshLayout mSwipeToRefreshLayout;

    private BroadcastReceiver mSyncCalled = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            mSwipeToRefreshLayout.setRefreshing(true);
        }
    };
    private BroadcastReceiver mRetrofitConnectionErrorReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            showMessage(MESSAGE_TYPE.ERROR_NO_CONNECTION);
        }
    };
    private BroadcastReceiver mRetrofitUnknownErrorReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            showMessage(MESSAGE_TYPE.ERROR_UNKNOWN);
        }
    };
    private BroadcastReceiver mNotificationsReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            showNotificationIfExists();
        }
    };

    @Override
    public void onResume() {
        super.onResume();
        registerReceivers();
    }

    @Override
    public void onPause() {
        unregisterReceivers();
        super.onPause();
    }

    @Override
    public void onCreate(Bundle paramBundle) {
        this.context = getActivity();
        super.onCreate(paramBundle);
    }

    @Override
    public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {

        rootView = paramLayoutInflater.inflate(R.layout.fragment_dashboard, paramViewGroup, false);

        activity = (AppCompatActivity) getActivity();

        mSwipeToRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.dashboard_swipe_refresh);
        mSwipeToRefreshLayout.setOnRefreshListener(this);
        mSwipeToRefreshLayout.setColorSchemeResources(R.color.swipe_refresh);

        mAvailableHolder = (LinearLayout) rootView.findViewById(R.id.fragment_dashboard_available_holder);
        mReviewsHolder = (CardView) rootView.findViewById(R.id.fragment_dashboard_reviews_holder);
        mProgressHolder = (CardView) rootView.findViewById(R.id.fragment_dashboard_progress_holder);
        mRecentUnlocksFragmentHolder = (LinearLayout) rootView.findViewById(R.id.fragment_dashboard_recent_unlocks_holder);
        mCriticalItemsFragmentHolder = (LinearLayout) rootView.findViewById(R.id.fragment_dashboard_critical_items_holder);

        mMessageCardHolder = (CardView) rootView.findViewById(R.id.fragment_dashboard_message_card_holder);
        mNotificationsCardHolder = (CardView) rootView.findViewById(R.id.fragment_dashboard_notifications_card_holder);
        mVacationModeCardHolder = (CardView) rootView.findViewById(R.id.fragment_dashboard_vacation_mode_card_holder);

        mVacationModeCard = (FrameLayout) rootView.findViewById(R.id.fragment_dashboard_vacation_mode_card);
        mReviewsCard = (FrameLayout) rootView.findViewById(R.id.fragment_dashboard_reviews_card);
        mProgressCard = (FrameLayout) rootView.findViewById(R.id.fragment_dashboard_progress_card);

        mProgressHolder.setOnClickListener(this);

        FragmentManager fragmentManager = activity.getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();

        VacationModeCard vacationModeCard = new VacationModeCard();
        AvailableCard availableCard = new AvailableCard();
        ReviewsCard reviewsCard = new ReviewsCard();
        SRSCard statusCard = new SRSCard();
        ProgressCard progressCard = new ProgressCard();
        RecentUnlocksCard recentUnlocksCard = new RecentUnlocksCard();
        CriticalItemsCard criticalItemsCard = new CriticalItemsCard();

        availableCard.setListener(this, getActivity());
        reviewsCard.setListener(this, getActivity());
        statusCard.setListener(this, getActivity());
        progressCard.setListener(this, getActivity());
        recentUnlocksCard.setListener(this, getActivity());
        criticalItemsCard.setListener(this, getActivity());

        transaction.replace(R.id.fragment_dashboard_vacation_mode_card, vacationModeCard);
        transaction.replace(R.id.fragment_dashboard_available_card, availableCard);
        transaction.replace(R.id.fragment_dashboard_reviews_card, reviewsCard);
        transaction.replace(R.id.fragment_dashboard_status_card, statusCard);
        transaction.replace(R.id.fragment_dashboard_progress_card, progressCard);
        transaction.replace(R.id.fragment_dashboard_recent_unlocks_card, recentUnlocksCard);
        transaction.replace(R.id.fragment_dashboard_critical_items_card, criticalItemsCard);
        transaction.commit();

        if (!MainActivity.isFirstSyncDashboardDone) {
            Intent intent = new Intent(BroadcastIntents.SYNC());
            LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
            checkVacationMode();
            MainActivity.isFirstSyncDashboardDone = true;
        } else {
            Intent intent = new Intent(BroadcastIntents.SYNC());
            LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
            checkVacationMode();
        }

        showNotificationIfExists();

        setRefreshing();

        return rootView;
    }

    private void setRefreshing() {
        if (mSwipeToRefreshLayout != null)
            mSwipeToRefreshLayout.post(new Runnable() {
                @Override
                public void run() {
                    mSwipeToRefreshLayout.setRefreshing(true);
                }
            });
    }

    private void updateSyncStatus() {
        if (isAvailableCardSynced && isReviewsCardSynced && isStatusCardSynced && isProgressCardSynced && isRecentUnlocksCardSynced && isCriticalItemsCardSynced) {
            mSwipeToRefreshLayout.setRefreshing(false);

            if (isAvailableCardSyncedSuccess && isReviewsCardSyncedSuccess && isStatusCardSyncedSuccess && isRecentUnlocksCardSyncedSuccess
                    && isCriticalItemsCardSyncedSuccess) {
                PrefManager.setDashboardLastUpdateDate(System.currentTimeMillis());
                onMessageCardOkButtonClick();
            }
        }
    }

    private void registerReceivers() {
        LocalBroadcastManager.getInstance(activity).registerReceiver(mSyncCalled,
                new IntentFilter(BroadcastIntents.SYNC()));
        LocalBroadcastManager.getInstance(activity).registerReceiver(mRetrofitConnectionErrorReceiver,
                new IntentFilter(BroadcastIntents.RETROFIT_ERROR_CONNECTION()));
        LocalBroadcastManager.getInstance(activity).registerReceiver(mRetrofitUnknownErrorReceiver,
                new IntentFilter(BroadcastIntents.RETROFIT_ERROR_UNKNOWN()));
        LocalBroadcastManager.getInstance(activity).registerReceiver(mNotificationsReceiver,
                new IntentFilter(BroadcastIntents.NOTIFICATION()));
    }

    private void unregisterReceivers() {
        LocalBroadcastManager.getInstance(activity).unregisterReceiver(mSyncCalled);
        LocalBroadcastManager.getInstance(activity).unregisterReceiver(mRetrofitConnectionErrorReceiver);
        LocalBroadcastManager.getInstance(activity).unregisterReceiver(mRetrofitUnknownErrorReceiver);
        LocalBroadcastManager.getInstance(activity).unregisterReceiver(mNotificationsReceiver);
    }

    private void showMessage(MESSAGE_TYPE msgType) {
        if (getActivity() == null) return;

        try {
            FragmentManager fragmentManager = activity.getSupportFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            MessageCard fragment = new MessageCard();
            fragment.setListener(this);

            String title = "";
            String prefix = "";

            if (msgType == MESSAGE_TYPE.ERROR_NO_CONNECTION) {
                title = getString(R.string.error_no_connection);
                prefix = getString(R.string.content_last_updated) + " ";
            }

            if (msgType == MESSAGE_TYPE.ERROR_UNKNOWN) {
                title = getString(R.string.error_unknown_error);
                prefix = getString(R.string.content_last_updated) + " ";
            }

            Bundle args = new Bundle();
            args.putString(MessageCard.ARG_TITLE, title);
            args.putString(MessageCard.ARG_PREFIX, prefix);
            args.putLong(MessageCard.ARG_TIME, PrefManager.getDashboardLastUpdateTime());
            fragment.setArguments(args);

            transaction.replace(R.id.fragment_dashboard_message_card, fragment).commit();

            mMessageCardHolder.setVisibility(View.VISIBLE);
        } catch (IllegalStateException e) {
            e.printStackTrace();
            // Probably http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
            // Ignore. No need to show message if Activity has been killed.
        }
    }

    private void showNotificationIfExists() {
        List<Notification> notifications = DatabaseManager.getNotifications();

        if (notifications != null && notifications.size() != 0) {
            FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
            Bundle args = new Bundle();
            args.putSerializable(NotificationsCard.ARG_NOTIFICATIONS, (Serializable) notifications);
            NotificationsCard card = new NotificationsCard();
            card.setArguments(args);
            transaction.replace(R.id.fragment_dashboard_notifications_card, card).commit();
            mNotificationsCardHolder.setVisibility(View.VISIBLE);
        } else {
            mNotificationsCardHolder.setVisibility(View.GONE);
        }
    }

    private void setCriticalItemsFragmentHeight(int height) {
        ViewGroup.LayoutParams params = mCriticalItemsFragmentHolder.getLayoutParams();
        params.height = height;
        mCriticalItemsFragmentHolder.setLayoutParams(params);
    }

    private void setRecentUnlocksFragmentHeight(int height) {
        ViewGroup.LayoutParams params = mRecentUnlocksFragmentHolder.getLayoutParams();
        params.height = height;
        mRecentUnlocksFragmentHolder.setLayoutParams(params);
    }

    private void checkVacationMode() {
        User user = DatabaseManager.getUser();
        if (user == null) return;

        if (user.isVacationModeActive()) {
            mAvailableHolder.setVisibility(View.GONE);
            mReviewsHolder.setVisibility(View.GONE);
            mVacationModeCardHolder.setVisibility(View.VISIBLE);
        } else {
            mAvailableHolder.setVisibility(View.VISIBLE);
            mReviewsHolder.setVisibility(View.VISIBLE);
            mVacationModeCardHolder.setVisibility(View.GONE);
        }
    }

    @Override
    public void onAvailableCardSyncFinishedListener(String result) {
        if (result.equals(SYNC_RESULT_SUCCESS))
            isAvailableCardSyncedSuccess = true;

        if (result.equals(SYNC_RESULT_FAILED))
            isAvailableCardSyncedSuccess = false;

        isAvailableCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onReviewsCardSyncFinishedListener(String result) {
        if (result.equals(SYNC_RESULT_SUCCESS))
            isReviewsCardSyncedSuccess = true;

        if (result.equals(SYNC_RESULT_FAILED))
            isReviewsCardSyncedSuccess = false;

        isReviewsCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onStatusCardSyncFinishedListener(String result) {
        if (result.equals(SYNC_RESULT_SUCCESS))
            isStatusCardSyncedSuccess = true;

        if (result.equals(SYNC_RESULT_FAILED))
            isStatusCardSyncedSuccess = false;

        isStatusCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onProgressCardSyncFinishedListener(String result) {
        if (result.equals(SYNC_RESULT_SUCCESS))
            isProgressCardSyncedSuccess = true;

        if (result.equals(SYNC_RESULT_FAILED))
            isProgressCardSyncedSuccess = false;

        isProgressCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onRecentUnlocksCardSyncFinishedListener(int height, String result) {
        if (result.equals(SYNC_RESULT_SUCCESS)) {
            isRecentUnlocksCardSyncedSuccess = true;
        }

        if (result.equals(SYNC_RESULT_FAILED)) {
            isRecentUnlocksCardSyncedSuccess = false;
        }

        setRecentUnlocksFragmentHeight(height);
        isRecentUnlocksCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onCriticalItemsCardSyncFinishedListener(int height, String result) {
        if (result.equals(SYNC_RESULT_SUCCESS)) {
            isCriticalItemsCardSyncedSuccess = true;
        }

        if (result.equals(SYNC_RESULT_FAILED)) {
            isCriticalItemsCardSyncedSuccess = false;
        }

        setCriticalItemsFragmentHeight(height);
        isCriticalItemsCardSynced = true;
        updateSyncStatus();
    }

    @Override
    public void onMessageCardOkButtonClick() {
        mMessageCardHolder.setVisibility(View.GONE);
    }

    @Override
    public void onRefresh() {
        isAvailableCardSynced = false;
        isReviewsCardSynced = false;
        isStatusCardSynced = false;
        isProgressCardSynced = false;
        isRecentUnlocksCardSynced = false;
        isCriticalItemsCardSynced = false;

        isAvailableCardSyncedSuccess = false;
        isReviewsCardSyncedSuccess = false;
        isStatusCardSyncedSuccess = false;
        isProgressCardSyncedSuccess = false;
        isRecentUnlocksCardSyncedSuccess = false;
        isCriticalItemsCardSyncedSuccess = false;

        showNotificationIfExists();

        Intent intent = new Intent(BroadcastIntents.SYNC());
        LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
        checkVacationMode();
    }

    @Override
    public void onClick(View view) {
        if (view == mReviewsHolder) {
            // TODO - Handle reviews card stuff
        }
        if (view == mProgressHolder) {
            startActivity(new Intent(getActivity(), ProgressDetailsActivity.class));
        }
    }

    enum MESSAGE_TYPE {
        ERROR_NO_CONNECTION,
        ERROR_UNKNOWN
    }
} public abstract class WaniKaniApi {
    private static final String API_HOST = "https://www.wanikani.com/api/user/";

    private static WaniKaniService service;
    private static String API_KEY;

    static {
        init();
    }

    public static void init() {
        API_KEY = PrefManager.getApiKey();
        setupService();
    }

    private static void setupService() {
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
            httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            clientBuilder.addInterceptor(httpLoggingInterceptor);
        }

        Retrofit retrofit = new Retrofit.Builder()
                .client(clientBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(API_HOST)
                .build();

        service = retrofit.create(WaniKaniService.class);
    }

    public static Call<Request<User>> getUser() {
        return service.getUser(API_KEY);
    }

    public static Call<Request<User>> getUser(String apiKey) {
        return service.getUser(apiKey);
    }

    public static Call<Request<StudyQueue>> getStudyQueue() {
        return service.getStudyQueue(API_KEY);
    }

    public static Call<Request<LevelProgression>> getLevelProgression() {
        return service.getLevelProgression(API_KEY);
    }

    public static Call<Request<SRSDistribution>> getSRSDistribution() {
        return service.getSRSDistribution(API_KEY);
    }

    public static Call<Request<RecentUnlocksList>> getRecentUnlocksList(int limit) {
        return service.getRecentUnlocksList(API_KEY, limit);
    }

    public static Call<Request<CriticalItemsList>> getCriticalItemsList(int percentage) {
        return service.getCriticalItemsList(API_KEY, percentage);
    }

    public static Call<Request<RadicalsList>> getRadicalsList(String level) {
        return service.getRadicalsList(API_KEY, level);
    }

    public static Call<Request<KanjiList>> getKanjiList(String level) {
        return service.getKanjiList(API_KEY, level);
    }

    public static Call<Request<VocabularyList>> getVocabularyList(String level) {
        return service.getVocabularyList(API_KEY, level);
    }
} public abstract class ThroughDbCallback<T extends Request<B>, B extends Storable> implements Callback<T> {
    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        T result = response.body();
        if (result == null) return;

        final User userInfo = result.user_information;
        final B requestedInfo = result.requested_information;

        if (result.error == null && (userInfo != null || requestedInfo != null)) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (userInfo != null) {
                        userInfo.save();
                    }
                    if (requestedInfo != null) {
                        requestedInfo.save();
                    }
                }
            }).start();
        }
    }

    @Override
    public void onFailure(Call<T> call, Throwable t) {
        RetrofitErrorHandler.handleError(t);
    }
} public class BroadcastIntents {

    public static String SYNC() {
        return "action.SYNC";
    }

    public static String RETROFIT_ERROR_CONNECTION() {
        return "error.retrofit.CONNECTION";
    }

    public static String RETROFIT_ERROR_UNKNOWN() {
        return "error.retrofit.UNKNOWN";
    }

    public static String NOTIFICATION() {
        return "NOTIFICATION";
    }

} public class DatabaseManager {
    private static final String TAG = "Database Manager";

    private static SQLiteDatabase db;

    public static void init(Context context) {
        if (db == null) {
            db = DatabaseHelper.getInstance(context).getWritableDatabase();
        }
    }

    private static void addItem(BaseItem item) {
        ContentValues values = new ContentValues();
        values.put(ItemsTable.COLUMN_NAME_CHARACTER, item.getCharacter());
        values.put(ItemsTable.COLUMN_NAME_KANA, item.getKana());
        values.put(ItemsTable.COLUMN_NAME_MEANING, item.getMeaning());
        values.put(ItemsTable.COLUMN_NAME_IMAGE, item.getImage());
        values.put(ItemsTable.COLUMN_NAME_ONYOMI, item.getOnyomi());
        values.put(ItemsTable.COLUMN_NAME_KUNYOMI, item.getKunyomi());
        values.put(ItemsTable.COLUMN_NAME_IMPORTANT_READING, item.getImportantReading());
        values.put(ItemsTable.COLUMN_NAME_LEVEL, item.getLevel());
        values.put(ItemsTable.COLUMN_NAME_ITEM_TYPE, item.getType().toString());
        values.put(ItemsTable.COLUMN_NAME_SRS, item.getSrsLevel());
        values.put(ItemsTable.COLUMN_NAME_UNLOCKED_DATE, item.getUnlockDateInSeconds());
        values.put(ItemsTable.COLUMN_NAME_AVAILABLE_DATE, item.getAvailableDateInSeconds());
        values.put(ItemsTable.COLUMN_NAME_BURNED, item.isBurned() ? 1 : 0);
        values.put(ItemsTable.COLUMN_NAME_BURNED_DATE, item.getBurnedDateInSeconds());
        values.put(ItemsTable.COLUMN_NAME_MEANING_CORRECT, item.getMeaningCorrect());
        values.put(ItemsTable.COLUMN_NAME_MEANING_INCORRECT, item.getMeaningIncorrect());
        values.put(ItemsTable.COLUMN_NAME_MEANING_MAX_STREAK, item.getMeaningMaxStreak());
        values.put(ItemsTable.COLUMN_NAME_MEANING_CURRENT_STREAK, item.getMeaningCurrentStreak());
        values.put(ItemsTable.COLUMN_NAME_READING_CORRECT, item.getReadingCorrect());
        values.put(ItemsTable.COLUMN_NAME_READING_INCORRECT, item.getReadingIncorrect());
        values.put(ItemsTable.COLUMN_NAME_READING_MAX_STREAK, item.getReadingMaxStreak());
        values.put(ItemsTable.COLUMN_NAME_READING_CURRENT_STREAK, item.getReadingCurrentStreak());
        values.put(ItemsTable.COLUMN_NAME_MEANING_NOTE, item.getMeaningNote());
        values.put(ItemsTable.COLUMN_NAME_USER_SYNONYMS, item.getUserSynonymsAsString());
        values.put(ItemsTable.COLUMN_NAME_READING_NOTE, item.getReadingNote());

        db.insert(ItemsTable.TABLE_NAME, ItemsTable.COLUMN_NAME_NULLABLE, values);
    }

    public static void saveItems(List<BaseItem> list, BaseItem.ItemType type) {
        deleteAllItemsFromSameLevelAndType(list, type);

        for (BaseItem item : list)
            addItem(item);
    }

    public static ItemsList getItems(BaseItem.ItemType itemType, int[] levels) {
        ItemsList list = new ItemsList();

        for (int level : levels) {
            String whereClause = ItemsTable.COLUMN_NAME_ITEM_TYPE + " = ?" + " AND "
                    + ItemsTable.COLUMN_NAME_LEVEL + " = ?";
            String[] whereArgs = {
                    itemType.toString(),
                    String.valueOf(level)
            };

            Cursor c = null;

            try {
                c = db.query(
                        ItemsTable.TABLE_NAME,
                        ItemsTable.COLUMNS,
                        whereClause,
                        whereArgs,
                        null,
                        null,
                        null
                );

                if (c != null && c.moveToFirst()) {
                    do {
                        BaseItem item = new BaseItem(
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_ID)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_CHARACTER)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_KANA)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_IMAGE)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_ONYOMI)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_KUNYOMI)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_IMPORTANT_READING)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_LEVEL)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_ITEM_TYPE)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_SRS)),
                                c.getLong(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_UNLOCKED_DATE)),
                                c.getLong(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_AVAILABLE_DATE)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_BURNED)) == 1,
                                c.getLong(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_BURNED_DATE)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING_CORRECT)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING_INCORRECT)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING_MAX_STREAK)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING_CURRENT_STREAK)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_READING_CORRECT)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_READING_INCORRECT)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_READING_MAX_STREAK)),
                                c.getInt(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_READING_CURRENT_STREAK)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_MEANING_NOTE)),
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_USER_SYNONYMS)) != null
                                        ? c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_USER_SYNONYMS)).split(",")
                                        : null,
                                c.getString(c.getColumnIndexOrThrow(ItemsTable.COLUMN_NAME_READING_NOTE))
                        );
                        list.add(item);
                    } while (c.moveToNext());
                }
            } finally {
                if (c != null) {
                    c.close();
                }
            }
        }

        return list.size() != 0 ? list : null;
    }

    private static void deleteAllItemsFromSameLevelAndType(List<BaseItem> list, BaseItem.ItemType type) {
        HashSet<Integer> levels = new HashSet();

        for (BaseItem item : list)
            levels.add(item.getLevel());

        for (Integer level : levels) {
            String whereClause = ItemsTable.COLUMN_NAME_LEVEL + " = ?" + " AND "
                    + ItemsTable.COLUMN_NAME_ITEM_TYPE + " = ?";
            String[] whereArgs = {
                    String.valueOf(level),
                    type.toString()
            };

            db.delete(ItemsTable.TABLE_NAME, whereClause, whereArgs);
        }
    }

    private static void addRecentUnlock(UnlockItem item) {
        ContentValues values = new ContentValues();
        values.put(RecentUnlocksTable.COLUMN_NAME_CHARACTER, item.getCharacter());
        values.put(RecentUnlocksTable.COLUMN_NAME_KANA, item.getKana());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING, item.getMeaning());
        values.put(RecentUnlocksTable.COLUMN_NAME_IMAGE, item.getImage());
        values.put(RecentUnlocksTable.COLUMN_NAME_ONYOMI, item.getOnyomi());
        values.put(RecentUnlocksTable.COLUMN_NAME_KUNYOMI, item.getKunyomi());
        values.put(RecentUnlocksTable.COLUMN_NAME_IMPORTANT_READING, item.getImportantReading());
        values.put(RecentUnlocksTable.COLUMN_NAME_LEVEL, item.getLevel());
        values.put(RecentUnlocksTable.COLUMN_NAME_ITEM_TYPE, item.getType().toString());
        values.put(RecentUnlocksTable.COLUMN_NAME_SRS, item.getSrsLevel());
        values.put(RecentUnlocksTable.COLUMN_NAME_UNLOCKED_DATE, item.getUnlockDateInSeconds());
        values.put(RecentUnlocksTable.COLUMN_NAME_AVAILABLE_DATE, item.getAvailableDateInSeconds());
        values.put(RecentUnlocksTable.COLUMN_NAME_BURNED, item.isBurned() ? 1 : 0);
        values.put(RecentUnlocksTable.COLUMN_NAME_BURNED_DATE, item.getBurnedDateInSeconds());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING_CORRECT, item.getMeaningCorrect());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING_INCORRECT, item.getMeaningIncorrect());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING_MAX_STREAK, item.getMeaningMaxStreak());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING_CURRENT_STREAK, item.getMeaningCurrentStreak());
        values.put(RecentUnlocksTable.COLUMN_NAME_READING_CORRECT, item.getReadingCorrect());
        values.put(RecentUnlocksTable.COLUMN_NAME_READING_INCORRECT, item.getReadingIncorrect());
        values.put(RecentUnlocksTable.COLUMN_NAME_READING_MAX_STREAK, item.getReadingMaxStreak());
        values.put(RecentUnlocksTable.COLUMN_NAME_READING_CURRENT_STREAK, item.getReadingCurrentStreak());
        values.put(RecentUnlocksTable.COLUMN_NAME_MEANING_NOTE, item.getMeaningNote());
        values.put(RecentUnlocksTable.COLUMN_NAME_USER_SYNONYMS, item.getUserSynonymsAsString());
        values.put(RecentUnlocksTable.COLUMN_NAME_READING_NOTE, item.getReadingNote());

        db.insert(RecentUnlocksTable.TABLE_NAME, RecentUnlocksTable.COLUMN_NAME_NULLABLE, values);
    }

    public static void saveRecentUnlocks(List<UnlockItem> list) {
        deleteSameRecentUnlocks(list);

        for (UnlockItem item : list)
            addRecentUnlock(item);
    }

    public static RecentUnlocksList getRecentUnlocks(int limit) {
        RecentUnlocksList list = new RecentUnlocksList();

        Cursor c = null;

        try {
            c = db.query(
                    RecentUnlocksTable.TABLE_NAME,
                    RecentUnlocksTable.COLUMNS,
                    null,
                    null,
                    null,
                    null,
                    null,
                    String.valueOf(limit)
            );

            if (c != null && c.moveToFirst()) {
                do {
                    UnlockItem item = new UnlockItem(
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_ID)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_CHARACTER)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_KANA)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_IMAGE)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_ONYOMI)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_KUNYOMI)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_IMPORTANT_READING)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_LEVEL)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_ITEM_TYPE)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_SRS)),
                            c.getLong(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_UNLOCKED_DATE)),
                            c.getLong(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_AVAILABLE_DATE)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_BURNED)) == 1,
                            c.getLong(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_BURNED_DATE)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING_CORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING_INCORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING_MAX_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING_CURRENT_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_READING_CORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_READING_INCORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_READING_MAX_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_READING_CURRENT_STREAK)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_MEANING_NOTE)),
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_USER_SYNONYMS)) != null
                                    ? c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_USER_SYNONYMS)).split(",")
                                    : null,
                            c.getString(c.getColumnIndexOrThrow(RecentUnlocksTable.COLUMN_NAME_READING_NOTE))
                    );
                    list.add(item);
                } while (c.moveToNext());
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        return list.size() != 0 ? list : null;
    }

    public static void deleteSameRecentUnlocks(List<UnlockItem> list) {
        for (UnlockItem item : list) {
            String whereClause = item.getImage() == null
                    ? RecentUnlocksTable.COLUMN_NAME_CHARACTER + " = ?"
                    : RecentUnlocksTable.COLUMN_NAME_IMAGE + " = ?";
            String[] whereArgs = {
                    item.getImage() == null ? String.valueOf(item.getCharacter()) : item.getImage()
            };

            db.delete(RecentUnlocksTable.TABLE_NAME, whereClause, whereArgs);
        }
    }

    private static void addCriticalItem(CriticalItem item) {
        if (item == null) return; // WANIKANI API SMH

        ContentValues values = new ContentValues();
        values.put(CriticalItemsTable.COLUMN_NAME_CHARACTER, item.getCharacter());
        values.put(CriticalItemsTable.COLUMN_NAME_KANA, item.getKana());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING, item.getMeaning());
        values.put(CriticalItemsTable.COLUMN_NAME_IMAGE, item.getImage());
        values.put(CriticalItemsTable.COLUMN_NAME_ONYOMI, item.getOnyomi());
        values.put(CriticalItemsTable.COLUMN_NAME_KUNYOMI, item.getKunyomi());
        values.put(CriticalItemsTable.COLUMN_NAME_IMPORTANT_READING, item.getImportantReading());
        values.put(CriticalItemsTable.COLUMN_NAME_LEVEL, item.getLevel());
        values.put(CriticalItemsTable.COLUMN_NAME_ITEM_TYPE, item.getType().toString());
        values.put(CriticalItemsTable.COLUMN_NAME_SRS, item.getSrsLevel());
        values.put(CriticalItemsTable.COLUMN_NAME_UNLOCKED_DATE, item.getUnlockDateInSeconds());
        values.put(CriticalItemsTable.COLUMN_NAME_AVAILABLE_DATE, item.getAvailableDateInSeconds());
        values.put(CriticalItemsTable.COLUMN_NAME_BURNED, item.isBurned() ? 1 : 0);
        values.put(CriticalItemsTable.COLUMN_NAME_BURNED_DATE, item.getBurnedDateInSeconds());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING_CORRECT, item.getMeaningCorrect());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING_INCORRECT, item.getMeaningIncorrect());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING_MAX_STREAK, item.getMeaningMaxStreak());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING_CURRENT_STREAK, item.getMeaningCurrentStreak());
        values.put(CriticalItemsTable.COLUMN_NAME_READING_CORRECT, item.getReadingCorrect());
        values.put(CriticalItemsTable.COLUMN_NAME_READING_INCORRECT, item.getReadingIncorrect());
        values.put(CriticalItemsTable.COLUMN_NAME_READING_MAX_STREAK, item.getReadingMaxStreak());
        values.put(CriticalItemsTable.COLUMN_NAME_READING_CURRENT_STREAK, item.getReadingCurrentStreak());
        values.put(CriticalItemsTable.COLUMN_NAME_MEANING_NOTE, item.getMeaningNote());
        values.put(CriticalItemsTable.COLUMN_NAME_USER_SYNONYMS, item.getUserSynonymsAsString());
        values.put(CriticalItemsTable.COLUMN_NAME_READING_NOTE, item.getReadingNote());
        values.put(CriticalItemsTable.COLUMN_NAME_PERCENTAGE, item.getPercentage());

        db.insert(CriticalItemsTable.TABLE_NAME, CriticalItemsTable.COLUMN_NAME_NULLABLE, values);
    }

    public static void saveCriticalItems(CriticalItemsList list) {
        deleteSameCriticalItems(list);

        for (CriticalItem item : list)
            addCriticalItem(item);
    }

    public static CriticalItemsList getCriticalItems(int percentage) {
        CriticalItemsList list = new CriticalItemsList();

        String whereClause = CriticalItemsTable.COLUMN_NAME_PERCENTAGE + " < ?";
        String[] whereArgs = {
                String.valueOf(percentage)
        };

        Cursor c = null;

        try {
            c = db.query(
                    CriticalItemsTable.TABLE_NAME,
                    CriticalItemsTable.COLUMNS,
                    whereClause,
                    whereArgs,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                do {
                    CriticalItem item = new CriticalItem(
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_ID)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_CHARACTER)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_KANA)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_IMAGE)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_ONYOMI)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_KUNYOMI)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_IMPORTANT_READING)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_LEVEL)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_ITEM_TYPE)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_SRS)),
                            c.getLong(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_UNLOCKED_DATE)),
                            c.getLong(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_AVAILABLE_DATE)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_BURNED)) == 1,
                            c.getLong(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_BURNED_DATE)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING_CORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING_INCORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING_MAX_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING_CURRENT_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_READING_CORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_READING_INCORRECT)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_READING_MAX_STREAK)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_READING_CURRENT_STREAK)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_MEANING_NOTE)),
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_USER_SYNONYMS)) != null
                                    ? c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_USER_SYNONYMS)).split(",")
                                    : null,
                            c.getString(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_READING_NOTE)),
                            c.getInt(c.getColumnIndexOrThrow(CriticalItemsTable.COLUMN_NAME_PERCENTAGE))
                    );
                    list.add(item);
                } while (c.moveToNext());
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        return list;
    }

    private static void deleteSameCriticalItems(List<CriticalItem> list) {
        for (CriticalItem item : list) {
            if (item == null) continue;

            String whereClause = item.getImage() == null
                    ? CriticalItemsTable.COLUMN_NAME_CHARACTER + " = ?"
                    : CriticalItemsTable.COLUMN_NAME_IMAGE + " = ?";
            String[] whereArgs = {
                    item.getImage() == null ? String.valueOf(item.getCharacter()) : item.getImage()
            };

            db.delete(CriticalItemsTable.TABLE_NAME, whereClause, whereArgs);
        }
    }

    public static void saveStudyQueue(StudyQueue queue) {
        deleteStudyQueue();

        ContentValues values = new ContentValues();
        values.put(StudyQueueTable.COLUMN_NAME_LESSONS_AVAILABLE, queue.lessons_available);
        values.put(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE, queue.reviews_available);
        values.put(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE_NEXT_HOUR, queue.reviews_available_next_hour);
        values.put(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE_NEXT_DAY, queue.reviews_available_next_day);
        values.put(StudyQueueTable.COLUMN_NAME_NEXT_REVIEW_DATE, queue.next_review_date);

        db.insert(StudyQueueTable.TABLE_NAME, StudyQueueTable.COLUMN_NAME_NULLABLE, values);
    }

    public static StudyQueue getStudyQueue() {
        Cursor c = null;

        try {
            c = db.query(
                    StudyQueueTable.TABLE_NAME,
                    StudyQueueTable.COLUMNS,
                    null,
                    null,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                return new StudyQueue(
                        c.getInt(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_ID)),
                        c.getInt(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_LESSONS_AVAILABLE)),
                        c.getInt(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE)),
                        c.getInt(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE_NEXT_HOUR)),
                        c.getInt(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_REVIEWS_AVAILABLE_NEXT_DAY)),
                        c.getLong(c.getColumnIndexOrThrow(StudyQueueTable.COLUMN_NAME_NEXT_REVIEW_DATE))
                );
            } else {
                Log.e(TAG, "No study queue found; returning null");
                return null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    public static void deleteStudyQueue() {
        db.delete(StudyQueueTable.TABLE_NAME, null, null);
    }

    public static void saveLevelProgression(LevelProgression progression) {
        deleteLevelProgression();

        ContentValues values = new ContentValues();
        values.put(LevelProgressionTable.COLUMN_NAME_RADICALS_PROGRESS, progression.radicals_progress);
        values.put(LevelProgressionTable.COLUMN_NAME_RADICALS_TOTAL, progression.radicals_total);
        values.put(LevelProgressionTable.COLUMN_NAME_REVIEWS_KANJI_PROGRESS, progression.kanji_progress);
        values.put(LevelProgressionTable.COLUMN_NAME_REVIEWS_KANJI_TOTAL, progression.kanji_total);

        db.insert(LevelProgressionTable.TABLE_NAME, LevelProgressionTable.COLUMN_NAME_NULLABLE, values);
    }

    public static LevelProgression getLevelProgression() {
        Cursor c = null;

        try {
            c = db.query(
                    LevelProgressionTable.TABLE_NAME,
                    LevelProgressionTable.COLUMNS,
                    null,
                    null,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                return new LevelProgression(
                        c.getInt(c.getColumnIndexOrThrow(LevelProgressionTable.COLUMN_NAME_ID)),
                        c.getInt(c.getColumnIndexOrThrow(LevelProgressionTable.COLUMN_NAME_RADICALS_PROGRESS)),
                        c.getInt(c.getColumnIndexOrThrow(LevelProgressionTable.COLUMN_NAME_RADICALS_TOTAL)),
                        c.getInt(c.getColumnIndexOrThrow(LevelProgressionTable.COLUMN_NAME_REVIEWS_KANJI_PROGRESS)),
                        c.getInt(c.getColumnIndexOrThrow(LevelProgressionTable.COLUMN_NAME_REVIEWS_KANJI_TOTAL))
                );
            } else {
                Log.e(TAG, "No study queue found; returning null");
                return null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    public static void deleteLevelProgression() {
        db.delete(SRSDistributionTable.TABLE_NAME, null, null);
    }

    public static void saveSrsDistribution(SRSDistribution distribution) {
        deleteSrsDistribution();

        ContentValues values = new ContentValues();
        values.put(SRSDistributionTable.COLUMN_NAME_APPRENTICE_RADICALS, distribution.apprentice.radicals);
        values.put(SRSDistributionTable.COLUMN_NAME_APPRENTICE_KANJI, distribution.apprentice.kanji);
        values.put(SRSDistributionTable.COLUMN_NAME_APPRENTICE_VOCABULARY, distribution.apprentice.vocabulary);
        values.put(SRSDistributionTable.COLUMN_NAME_GURU_RADICALS, distribution.guru.radicals);
        values.put(SRSDistributionTable.COLUMN_NAME_GURU_KANJI, distribution.guru.kanji);
        values.put(SRSDistributionTable.COLUMN_NAME_GURU_VOCABULARY, distribution.guru.vocabulary);
        values.put(SRSDistributionTable.COLUMN_NAME_MASTER_RADICALS, distribution.master.radicals);
        values.put(SRSDistributionTable.COLUMN_NAME_MASTER_KANJI, distribution.master.kanji);
        values.put(SRSDistributionTable.COLUMN_NAME_MASTER_VOCABULARY, distribution.master.vocabulary);
        values.put(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_RADICALS, distribution.enlighten.radicals);
        values.put(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_KANJI, distribution.enlighten.kanji);
        values.put(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_VOCABULARY, distribution.enlighten.vocabulary);
        values.put(SRSDistributionTable.COLUMN_NAME_BURNED_RADICALS, distribution.burned.radicals);
        values.put(SRSDistributionTable.COLUMN_NAME_BURNED_KANJI, distribution.burned.kanji);
        values.put(SRSDistributionTable.COLUMN_NAME_BURNED_VOCABULARY, distribution.burned.vocabulary);

        db.insert(SRSDistributionTable.TABLE_NAME, SRSDistributionTable.COLUMN_NAME_NULLABLE, values);
    }

    public static SRSDistribution getSrsDistribution() {
        Cursor c = null;

        try {
            c = db.query(
                    SRSDistributionTable.TABLE_NAME,
                    SRSDistributionTable.COLUMNS,
                    null,
                    null,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                return new SRSDistribution(
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_ID)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_APPRENTICE_RADICALS)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_APPRENTICE_KANJI)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_APPRENTICE_VOCABULARY)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_GURU_RADICALS)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_GURU_KANJI)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_GURU_VOCABULARY)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_MASTER_RADICALS)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_MASTER_KANJI)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_MASTER_VOCABULARY)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_RADICALS)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_KANJI)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_ENLIGHTENED_VOCABULARY)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_BURNED_RADICALS)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_BURNED_KANJI)),
                        c.getInt(c.getColumnIndexOrThrow(SRSDistributionTable.COLUMN_NAME_BURNED_VOCABULARY))
                );
            } else {
                Log.e(TAG, "No srs distribution found; returning null");
                return null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    public static void deleteSrsDistribution() {
        db.delete(SRSDistributionTable.TABLE_NAME, null, null);
    }

    private static void addUser(User user) {
        ContentValues values = new ContentValues();
        values.put(UsersTable.COLUMN_NAME_USERNAME, user.username);
        values.put(UsersTable.COLUMN_NAME_GRAVATAR, user.gravatar);
        values.put(UsersTable.COLUMN_NAME_LEVEL, user.level);
        values.put(UsersTable.COLUMN_NAME_TITLE, user.title);
        values.put(UsersTable.COLUMN_NAME_ABOUT, user.about);
        values.put(UsersTable.COLUMN_NAME_WEBSITE, user.website);
        values.put(UsersTable.COLUMN_NAME_TWITTER, user.twitter);
        values.put(UsersTable.COLUMN_NAME_TOPICS_COUNT, user.topics_count);
        values.put(UsersTable.COLUMN_NAME_POSTS_COUNT, user.posts_count);
        values.put(UsersTable.COLUMN_NAME_CREATION_DATE, user.creation_date);
        values.put(UsersTable.COLUMN_NAME_VACATION_DATE, user.vacation_date);
        db.insert(UsersTable.TABLE_NAME, UsersTable.COLUMN_NAME_NULLABLE, values);
    }

    public static void saveUser(User user) {
        deleteUsers();

        addUser(user);
    }

    public static User getUser() {
        Cursor c = null;

        try {
            c = db.query(
                    UsersTable.TABLE_NAME,
                    UsersTable.COLUMNS,
                    null,
                    null,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                return new User(
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_USERNAME)),
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_GRAVATAR)),
                        c.getInt(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_LEVEL)),
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_TITLE)),
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_ABOUT)),
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_WEBSITE)),
                        c.getString(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_TWITTER)),
                        c.getInt(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_TOPICS_COUNT)),
                        c.getInt(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_POSTS_COUNT)),
                        c.getLong(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_CREATION_DATE)),
                        c.getLong(c.getColumnIndexOrThrow(UsersTable.COLUMN_NAME_VACATION_DATE))
                );
            } else {
                Log.e(TAG, "No users found; returning null");
                return null;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    public static void deleteUsers() {
        db.delete(UsersTable.TABLE_NAME, null, null);
    }

    public static void saveNotification(Notification item) {
        db.delete(
                NotificationsTable.TABLE_NAME,
                NotificationsTable.COLUMN_NAME_ID + " = ?",
                new String[]{String.valueOf(item.getId())}
        );

        ContentValues values = new ContentValues();
        values.put(NotificationsTable.COLUMN_NAME_ID, item.getId());
        values.put(NotificationsTable.COLUMN_NAME_TITLE, item.getTitle());
        values.put(NotificationsTable.COLUMN_NAME_SHORT_TEXT, item.getShortText());
        values.put(NotificationsTable.COLUMN_NAME_TEXT, item.getText());
        values.put(NotificationsTable.COLUMN_NAME_IMAGE, item.getImage());
        values.put(NotificationsTable.COLUMN_NAME_ACTION_URL, item.getActionUrl());
        values.put(NotificationsTable.COLUMN_NAME_READ, item.isRead() ? 1 : 0);
        values.put(NotificationsTable.COLUMN_NAME_ACTION_TEXT, item.getActionText());

        db.insert(NotificationsTable.TABLE_NAME, NotificationsTable.COLUMN_NAME_NULLABLE, values);
    }

    public static void saveNotifications(List<Notification> list) {
        deleteSameNotifications(list);

        for (Notification item : list)
            saveNotification(item);
    }

    public static List<Notification> getNotifications() {
        List<Notification> list = new ArrayList<>();

        String whereClause = NotificationsTable.COLUMN_NAME_READ + " = ?";
        String[] whereArgs = {
                "0"
        };

        Cursor c = null;

        try {
            c = db.query(
                    NotificationsTable.TABLE_NAME,
                    NotificationsTable.COLUMNS,
                    whereClause,
                    whereArgs,
                    null,
                    null,
                    null
            );

            if (c != null && c.moveToFirst()) {
                do {
                    Notification item = new Notification(
                            c.getInt(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_ID)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_TITLE)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_SHORT_TEXT)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_TEXT)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_IMAGE)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_ACTION_URL)),
                            c.getString(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_ACTION_TEXT)),
                            c.getInt(c.getColumnIndexOrThrow(NotificationsTable.COLUMN_NAME_READ)) == 1
                    );
                    list.add(item);
                } while (c.moveToNext());
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        return list;
    }

    private static void deleteSameNotifications(List<Notification> list) {
        for (Notification item : list) {
            String whereClause = NotificationsTable.COLUMN_NAME_ID + " = ?";
            String[] whereArgs = {
                    String.valueOf(item.getId())
            };

            db.delete(NotificationsTable.TABLE_NAME, whereClause, whereArgs);
        }
    }
} @SuppressLint("CommitPrefEdits")
public abstract class PrefManager {
    public static final String PREF_API_KEY = "pref_api_key";
    private static final String PREF_DASHBOARD_RECENT_UNLOCKS_NUMBER = "pref_dashboard_recent_unlock_number";
    private static final String PREF_DASHBOARD_CRITICAL_ITEMS_PERCENTAGE = "pref_dashboard_critical_items_percentage";
    private static final String PREF_CRITICAL_ITEMS_NUMBER = "pref_critical_items_number";
    private static final String PREF_USE_CUSTOM_FONTS = "pref_use_custom_fonts";
    private static final String PREF_USE_SPECIFIC_DATES = "pref_use_specific_dates";
    private static final String PREF_REVIEWS_IMPROVEMENTS = "pref_reviews_improvements";
    private static final String PREF_IGNORE_BUTTON = "pref_ignore_button";
    private static final String PREF_SINGLE_BUTTON = "pref_single_button";
    private static final String PREF_PORTRAIT_MODE = "pref_portrait_mode";
    private static final String PREF_WANIKANI_IMPROVE = "pref_wanikani_improve";
    private static final String PREF_REVIEW_ORDER = "pref_review_order";
    private static final String PREF_LESSON_ORDER = "pref_lesson_order";
    private static final String PREF_EXTERNAL_FRAME_PLACER = "pref_eternal_frame_placer";
    private static final String PREF_EXTERNAL_FRAME_PLACER_DICTIONARY = "pref_external_frame_placer_dictionary";
    private static final String PREF_PART_OF_SPEECH = "pref_part_of_speech";
    private static final String PREF_AUTO_POPUP = "pref_auto_popup";
    private static final String PREF_MISTAKE_DELAY = "pref_mistake_delay";
    private static final String PREF_ROMAJI = "pref_romaji";
    private static final String PREF_NO_SUGGESTION = "pref_no_suggestions";
    private static final String PREF_MUTE_BUTTON = "pref_mute_button";
    private static final String PREF_SRS_INDCATION = "pref_srs_indication";
    private static final String PREF_IGNORE_BUTTON_MESSAGE = "pref_ignore_button_message";
    private static final String PREF_HW_ACCEL_MESSAGE = "pref_hw_accel_message";
    private static final String PREF_MUTE = "pref_mute";
    private static final String PREF_HW_ACCEL = "pref_hw_accel";
    private static final String PREF_REVIEWS_LESSONS_FULLSCREEN = "pref_rev_les_fullscreen";
    private static final String PREF_SHOW_NOTIFICATIONS = "pref_show_notifications";
    private static final String PREF_ENABLE_REMINDER_NOTIFICATION = "pref_enable_reminder_notification";
    private static final String PREF_REMINDER_NOTIFICATION_INTERVAL = "pref_reminder_notification_interval";

    private static SharedPreferences prefs;
    private static SharedPreferences reviewsPrefs;

    public static void init(Context context) {
        prefs = PreferenceManager.getDefaultSharedPreferences(context);
        reviewsPrefs = context.getSharedPreferences("review-lessons_prefs", Context.MODE_PRIVATE);
    }

    public static String getApiKey() {
        return prefs.getString("api_key", "0");
    }

    public static void setApiKey(String key) {
        prefs.edit().putString("api_key", key).commit();
    }

    public static boolean isFirstLaunch() {
        return prefs.getBoolean("first_launch", true);
    }

    public static void setFirstLaunch(boolean value) {
        prefs.edit().putBoolean("first_launch", value).commit();
    }

    public static boolean isProfileFirstTime() {
        return prefs.getBoolean("first_time_profile", true);
    }

    public static void setProfileFirstTime(boolean value) {
        prefs.edit().putBoolean("first_time_profile", value).commit();
    }

    public static int getDashboardRecentUnlocksNumber() {
        return prefs.getInt(PREF_DASHBOARD_RECENT_UNLOCKS_NUMBER, 5);
    }

    public static void setDashboardRecentUnlocksNumber(int number) {
        prefs.edit().putInt(PREF_DASHBOARD_RECENT_UNLOCKS_NUMBER, number).commit();
    }

    public static int getDashboardCriticalItemsPercentage() {
        return prefs.getInt(PREF_DASHBOARD_CRITICAL_ITEMS_PERCENTAGE, 75);
    }

    public static void setDashboardCriticalItemsPercentage(int number) {
        prefs.edit().putInt(PREF_DASHBOARD_CRITICAL_ITEMS_PERCENTAGE, number).commit();
    }

    public static int getCriticalItemsNumber() {
        return prefs.getInt(PREF_CRITICAL_ITEMS_NUMBER, 5);
    }

    public static void setCriticalItemsNumber(int value) {
        prefs.edit().putInt(PREF_CRITICAL_ITEMS_NUMBER, value).commit();
    }

    public static boolean isLegendLearned() {
        return prefs.getBoolean("pref_legend_learned", false);
    }

    public static void setLegendLearned(boolean value) {
        prefs.edit().putBoolean("pref_legend_learned", value).commit();
    }

    public static void setDashboardLastUpdateDate(long date) {
        prefs.edit().putLong("pref_update_date_dashboard", date).commit();
    }

    public static long getDashboardLastUpdateTime() {
        return prefs.getLong("pref_update_date_dashboard", 0);
    }

    public static boolean isUseCustomFonts() {
        return prefs.getBoolean(PREF_USE_CUSTOM_FONTS, true);
    }

    public static void setUseCUstomFonts(boolean value) {
        prefs.edit().putBoolean(PREF_USE_CUSTOM_FONTS, value).commit();
    }

    public static boolean isUseSpecificDates() {
        return prefs.getBoolean(PREF_USE_SPECIFIC_DATES, false);
    }

    public static void setUseSpecificDates(boolean value) {
        prefs.edit().putBoolean(PREF_USE_SPECIFIC_DATES, value).commit();
    }

    public static boolean getReviewsImprovements() {
        return prefs.getBoolean(PREF_REVIEWS_IMPROVEMENTS, true);
    }

    public static void setReviewsImprovements(boolean value) {
        prefs.edit().putBoolean(PREF_REVIEWS_IMPROVEMENTS, value).commit();
    }

    public static boolean getIgnoreButton() {
        return prefs.getBoolean(PREF_IGNORE_BUTTON, true);
    }

    public static void setIgnoreButton(boolean value) {
        prefs.edit().putBoolean(PREF_IGNORE_BUTTON, value).commit();
    }

    public static boolean getSingleButton() {
        return prefs.getBoolean(PREF_SINGLE_BUTTON, true);
    }

    public static void setSingleButton(boolean value) {
        prefs.edit().putBoolean(PREF_SINGLE_BUTTON, value).commit();
    }

    public static boolean getPortraitMode() {
        return prefs.getBoolean(PREF_PORTRAIT_MODE, true);
    }

    public static void setPortraitMode(boolean value) {
        prefs.edit().putBoolean(PREF_PORTRAIT_MODE, value).commit();
    }

    public static boolean getWaniKaniImprove() {
        return prefs.getBoolean(PREF_WANIKANI_IMPROVE, false);
    }

    public static void setWaniKaniImprove(boolean value) {
        prefs.edit().putBoolean(PREF_WANIKANI_IMPROVE, value).commit();
    }

    public static boolean getReviewOrder() {
        return prefs.getBoolean(PREF_REVIEW_ORDER, false);
    }

    public static void setReviewOrder(boolean value) {
        prefs.edit().putBoolean(PREF_REVIEW_ORDER, value).commit();
    }

    public static boolean getLessonOrder() {
        return prefs.getBoolean(PREF_LESSON_ORDER, false);
    }

    public static void setLessonOrder(boolean value) {
        prefs.edit().putBoolean(PREF_LESSON_ORDER, value).commit();
    }

    public static boolean getExternalFramePlacer() {
        return prefs.getBoolean(PREF_EXTERNAL_FRAME_PLACER, false);
    }

    public static void setExternalFramePlacer(boolean value) {
        prefs.edit().putBoolean(PREF_EXTERNAL_FRAME_PLACER, value).commit();
    }

    public static ExternalFramePlacer.Dictionary getExternalFramePlacerDictionary() {
        ExternalFramePlacer.Dictionary dict;
        String tag = prefs.getString(PREF_EXTERNAL_FRAME_PLACER_DICTIONARY,
                ExternalFramePlacer.Dictionary.JISHO.name());
        dict = ExternalFramePlacer.Dictionary.valueOf(tag);
        dict = ExternalFramePlacer.Dictionary.JISHO;
        return dict;
    }

    public static void setExternalFramePlacerDictionary(String value) {
        prefs.edit().putString(PREF_EXTERNAL_FRAME_PLACER_DICTIONARY, value).commit();
    }

    public static boolean getPartOfSpeech() {
        return prefs.getBoolean(PREF_PART_OF_SPEECH, false); // TODO - Make true after integration
    }

    public static void setPartOfSpeech(boolean value) {
        prefs.edit().putBoolean(PREF_PART_OF_SPEECH, value).commit();
    }

    public static boolean getAutoPopup() {
        return prefs.getBoolean(PREF_AUTO_POPUP, false);
    }

    public static void setAutoPopup(boolean value) {
        prefs.edit().putBoolean(PREF_AUTO_POPUP, value).commit();
    }

    public static boolean getMistakeDelay() {
        return prefs.getBoolean(PREF_MISTAKE_DELAY, false);
    }

    public static void setMistakeDelay(boolean value) {
        prefs.edit().putBoolean(PREF_MISTAKE_DELAY, value).commit();
    }

    public static boolean getRomaji() {
        return prefs.getBoolean(PREF_ROMAJI, false);
    }

    public static void setRomaji(boolean value) {
        prefs.edit().putBoolean(PREF_ROMAJI, value).commit();
    }

    public static boolean getNoSuggestion() {
        return prefs.getBoolean(PREF_NO_SUGGESTION, true);
    }

    public static void setNoSuggestion(boolean value) {
        prefs.edit().putBoolean(PREF_NO_SUGGESTION, value).commit();
    }

    public static boolean getMuteButton() {
        return prefs.getBoolean(PREF_MUTE_BUTTON, true);
    }

    public static void setMuteButton(boolean value) {
        prefs.edit().putBoolean(PREF_MUTE_BUTTON, value).commit();
    }

    public static boolean getSRSIndication() {
        return prefs.getBoolean(PREF_SRS_INDCATION, true);
    }

    public static void setSRSIndication(boolean value) {
        prefs.edit().putBoolean(PREF_SRS_INDCATION, value).commit();
    }

    public static Keyboard getReviewsKeyboard() {
        return getReviewsImprovements() ? Keyboard.LOCAL_IME : Keyboard.NATIVE;
    }

    public static Intent getWebViewIntent(Context context) {
        boolean accel = getHWAccel();
        return new Intent(context, accel ? WebReviewActivity.class : SWWebReviewActivity.class);
    }

    public static boolean getIgnoreButtonMessage() {
        return reviewsPrefs.getBoolean(PREF_IGNORE_BUTTON_MESSAGE, true);
    }

    public static void setIgnoreButtonMessage(boolean value) {
        reviewsPrefs.edit().putBoolean(PREF_IGNORE_BUTTON_MESSAGE, value).commit();
    }

    public static boolean getHWAccelMessage() {
        return reviewsPrefs.getBoolean(PREF_HW_ACCEL_MESSAGE, true);
    }

    public static void setHWAccelMessage(boolean value) {
        reviewsPrefs.edit().putBoolean(PREF_HW_ACCEL_MESSAGE, value).commit();
    }

    public static boolean getHWAccel() {
        return prefs.getBoolean(PREF_HW_ACCEL, true);
    }

    public static void setHWAccel(boolean value) {
        prefs.edit().putBoolean(PREF_HW_ACCEL, value).commit();
    }

    public static boolean toggleMute() {
        boolean mute = !getMute();
        setMute(mute);
        return mute;
    }

    public static boolean getMute() {
        return prefs.getBoolean(PREF_MUTE, false);
    }

    public static void setMute(boolean value) {
        prefs.edit().putBoolean(PREF_MUTE, value).commit();
    }

    public static boolean getReviewsLessonsFullscreen() {
        return prefs.getBoolean(PREF_REVIEWS_LESSONS_FULLSCREEN, false);
    }

    public static void setReviewsLessonsFullscreen(boolean value) {
        prefs.edit().putBoolean(PREF_REVIEWS_LESSONS_FULLSCREEN, value).commit();
    }

    public static void setNotificationsEnabled(Context context, boolean value) {
        prefs.edit().putBoolean(PREF_SHOW_NOTIFICATIONS, value).commit();
        if (!value) {
            new NotificationScheduler(context).cancelNotifications();
        }
    }

    public static boolean notificationsEnabled() {
        return prefs.getBoolean(PREF_SHOW_NOTIFICATIONS, true);
    }

    public static boolean reminderNotificationEnabled() {
        return prefs.getBoolean(PREF_ENABLE_REMINDER_NOTIFICATION, true);
    }

    public static void setReminderNotificationEnabled(boolean value) {
        prefs.edit().putBoolean(PREF_ENABLE_REMINDER_NOTIFICATION, value).commit();
    }

    public static long getReminderNotificationInterval() {
        return prefs.getLong(PREF_REMINDER_NOTIFICATION_INTERVAL, 7200000); // 2 hours by default
    }

    public static void setReminderNotificationInterval(long milliseconds) {
        prefs.edit().putLong(PREF_REMINDER_NOTIFICATION_INTERVAL, milliseconds).commit();
    }

    public static void logout(Context context) {
        prefs.edit().clear().commit();
        reviewsPrefs.edit().clear().commit();

        File offlineData = new File(Environment.getDataDirectory() + "/data/" + context.getPackageName() + "/shared_prefs/offline_data.xml");
        File cacheDir = new File(Environment.getDataDirectory() + "/data/" + context.getPackageName() + "/cache");
        File webviewCacheDir = new File(Environment.getDataDirectory() + "/data/" + context.getPackageName() + "/app_webview");

        try {
            if (offlineData.exists()) {
                offlineData.delete();
            }
            FileUtils.deleteDirectory(cacheDir);
            FileUtils.deleteDirectory(webviewCacheDir);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Cancel the notification alarm...
        Intent notificationIntent = new Intent(context, NotificationPublisher.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                context,
                NotificationPublisher.REQUEST_CODE,
                notificationIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
        );
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }

    public enum Keyboard {
        LOCAL_IME, NATIVE
    }
} public class Request<T> {
    public User user_information;
    public T requested_information;
    public Error error;
} public class StudyQueue implements Serializable, Storable {
    public int id;
    public int lessons_available;
    public int reviews_available;
    public int reviews_available_next_hour;
    public int reviews_available_next_day;
    public long next_review_date;

    public StudyQueue(int id, int lessons, int reviews, int reviewsNextHour, int reviewsNextDay, long nextReviewDate) {
        this.id = id;
        this.lessons_available = lessons;
        this.reviews_available = reviews;
        this.reviews_available_next_hour = reviewsNextHour;
        this.reviews_available_next_day = reviewsNextDay;
        this.next_review_date = nextReviewDate;
    }

    public long getNextReviewDateInMillis() {
        return next_review_date * 1000;
    }

    @Override
    public void save() {
        DatabaseManager.saveStudyQueue(this);
    }
} public class User implements Serializable, Storable {
    public String username;
    public String gravatar;
    public int level;
    public String title;
    public String about;
    public String website;
    public String twitter;
    public int topics_count;
    public int posts_count;
    public long creation_date;
    public long vacation_date;

    public User(String username, String gravatar, int level, String title, String about,
                String website, String twitter, int topicsCount, int postsCount, long creationDate,
                long vacationDate) {
        this.username = username;
        this.gravatar = gravatar;
        this.level = level;
        this.title = title;
        this.about = about;
        this.website = website;
        this.twitter = twitter;
        this.topics_count = topicsCount;
        this.posts_count = postsCount;
        this.creation_date = creationDate;
        this.vacation_date = vacationDate;
    }

    public long getCreationDateInMillis() {
        return creation_date * 1000;
    }

    public long getVacationDateInMillis() {
        return vacation_date * 1000;
    }

    public boolean isVacationModeActive() {
        return getVacationDateInMillis() != 0;
    }

    @Override
    public void save() {
        DatabaseManager.saveUser(this);
    }
} public class Utils {
    Context context;

    public Utils(Context context) {
        this.context = context;
    }

    private static double removeMinusFromLong(double time) {
        if (time < 0) {
            String timeString = time + "";
            timeString = timeString.substring(1, timeString.length());
            return Double.parseDouble(timeString);
        } else return time;
    }

    public static Date getCurrentDate() {
        return new Date(System.currentTimeMillis());
    }

    public boolean isNetworkAvailable() {
        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null;
    }

    public float pxFromDp(float dp) {
        return dp * context.getResources().getDisplayMetrics().density;
    }

    /**
     * I hate myself for doing this but I really don't feel like looking at LevelPickerDialogFragment.
     * Who the hell keeps an int array as comma-delimited string! Someone kill me pls. ugh
     */
    public static int[] convertStringArrayToIntArray(String[] strings) {
        int[] ints = new int[strings.length];
        for (int i = 0; i < strings.length; i++) {
            ints[i] = Integer.parseInt(strings[i]);
        }
        return ints;
    }
}
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import retrofit2.Call;
import retrofit2.Response;
import tr.xip.wanikani.R;
import tr.xip.wanikani.app.fragment.DashboardFragment;
import tr.xip.wanikani.client.WaniKaniApi;
import tr.xip.wanikani.client.task.callback.ThroughDbCallback;
import tr.xip.wanikani.content.receiver.BroadcastIntents;
import tr.xip.wanikani.database.DatabaseManager;
import tr.xip.wanikani.managers.PrefManager;
import tr.xip.wanikani.models.Request;
import tr.xip.wanikani.models.StudyQueue;
import tr.xip.wanikani.models.User;
import tr.xip.wanikani.utils.Utils;
import tr.xip.wanikani.widget.RelativeTimeTextView;
package tr.xip.wanikani.app.fragment.card;




/**
 * Created by xihsa_000 on 3/13/14.
 */
public class ReviewsCard extends Fragment {

    Context context;
    Utils utils;

    View rootView;

    ReviewsCardListener mListener;

    RelativeTimeTextView mNextReview;
    TextView mNextHour;
    TextView mNextDay;

    LinearLayout mMoreReviewsHolder;
    TextView mMoreReviews;

    private BroadcastReceiver mDoLoad = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            WaniKaniApi.getStudyQueue().enqueue(new ThroughDbCallback<Request<StudyQueue>, StudyQueue>() {