public class ItemDetailsActivity extends AppCompatActivity {

    public static final String ARG_ITEM = "arg_item";
    private static final String TAG = "ItemDetailsActivity";
    private static final int FLIPPER_CONTENT = 0;
    private static final int FLIPPER_PROGRESS_BAR = 1;

    Toolbar mToolbar;

    TextView mTitle;
    ImageView mTitleImage;
    TextView mLevel;
    TextView mMeaning;

    CardView mItemCard;

    CardView mAlternativeMeaningsAndUserSynonymsCard;
    TextView mAlternativeMeanings;
    LinearLayout mAlternativeMeaningsHolder;
    TextView mUserSynonyms;
    LinearLayout mUserSynonymsHolder;

    CardView mReadingsCard;
    TextView mReading;
    LinearLayout mReadingHolder;
    TextView mOnyomi;
    LinearLayout mOnyomiHolder;
    TextView mKunyomi;
    LinearLayout mKunyomiHolder;

    TextView mReadingNote;
    CardView mReadingNoteCard;

    TextView mMeaningNote;
    CardView mMeaningNoteCard;

    LinearLayout mLocked;
    LinearLayout mBurned;
    TextView mBurnedDate;
    LinearLayout mProgressContent;

    CardView mProgressCard;

    TextView mAlternativeMeaningsTitle;
    TextView mUserSynonymsTitle;
    TextView mReadingTitle;
    TextView mMeaningNoteTitle;
    TextView mReadingNoteTitle;
    TextView mProgressTitle;

    ImageView mSRSLogo;
    TextView mSRSLevel;
    LinearLayout mUnlockHolder;
    TextView mUnlocked;
    LinearLayout mNextAvailableHolder;
    RelativeTimeTextView mNextAvailable;
    RelativeLayout mMeaningCorrectHolder;
    TextView mMeaningCorrectPercentage;
    RelativeLayout mMeaningIncorrectHolder;
    TextView mMeaningIncorrectPercentage;
    LinearLayout mMeaningMaxStreakHolder;
    TextView mMeaningMaxStreak;
    LinearLayout mMeaningCurrentStreakHolder;
    TextView mMeaningCurrentStreak;
    RelativeLayout mReadingCorrectHolder;
    TextView mReadingCorrectPercentage;
    RelativeLayout mReadingIncorrectHolder;
    TextView mReadingIncorrectPercentage;
    LinearLayout mReadingMaxStreakHolder;
    TextView mReadingMaxStreak;
    LinearLayout mReadingCurrentStreakHolder;
    TextView mReadingCurrentStreak;

    ProgressBar mMeaningCorrectProgressBar;
    ProgressBar mMeaningIncorrectProgressBar;
    ProgressBar mReadingCorrectProgressBar;
    ProgressBar mReadingIncorrectProgressBar;

    ObservableScrollView mScrollView;

    ViewFlipper mFlipper;

    BaseItem mItem;

    private int mToolbarHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Intent intent = getIntent();
        mItem = (BaseItem) intent.getSerializableExtra(ARG_ITEM);

        switch (mItem.getType()) {
            case RADICAL:
                setTheme(R.style.AppTheme_Radical);
                break;
            case KANJI:
                setTheme(R.style.AppTheme_Kanji);
                break;
            case VOCABULARY:
                setTheme(R.style.AppTheme_Vocabulary);
                break;
        }

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_item_details);

        setUpActionBar();

        mFlipper = (ViewFlipper) findViewById(R.id.details_view_flipper);

        mAlternativeMeaningsTitle = (TextView) findViewById(R.id.details_title_alternative_meanings);
        mUserSynonymsTitle = (TextView) findViewById(R.id.details_title_user_synonyms);
        mReadingTitle = (TextView) findViewById(R.id.details_title_reading);
        mMeaningNoteTitle = (TextView) findViewById(R.id.details_title_meaning_note);
        mReadingNoteTitle = (TextView) findViewById(R.id.details_title_reading_note);
        mProgressTitle = (TextView) findViewById(R.id.details_progress_title);

        setUpScrollView();

        mItemCard = (CardView) findViewById(R.id.item_card);

        mTitle = (TextView) findViewById(R.id.title);
        mTitleImage = (ImageView) findViewById(R.id.title_image);

        mLevel = (TextView) findViewById(R.id.details_level);
        mMeaning = (TextView) findViewById(R.id.details_meaning);

        mAlternativeMeaningsAndUserSynonymsCard = (CardView)
                findViewById(R.id.details_alternative_meanings_and_user_synonyms_card);

        mAlternativeMeanings = (TextView) findViewById(R.id.details_alternative_meanings);
        mAlternativeMeaningsHolder = (LinearLayout) findViewById(R.id.details_alternative_meanings_holder);
        mUserSynonyms = (TextView) findViewById(R.id.details_user_synonyms);
        mUserSynonymsHolder = (LinearLayout) findViewById(R.id.details_user_synonyms_holder);

        mReadingsCard = (CardView) findViewById(R.id.details_readings_card);

        mReading = (TextView) findViewById(R.id.details_reading);
        mReadingHolder = (LinearLayout) findViewById(R.id.details_readings_reading_holder);
        mOnyomi = (TextView) findViewById(R.id.details_readings_onyomi);
        mOnyomiHolder = (LinearLayout) findViewById(R.id.details_readings_onyomi_holder);
        mKunyomi = (TextView) findViewById(R.id.details_readings_kunyomi);
        mKunyomiHolder = (LinearLayout) findViewById(R.id.details_readings_kunyomi_holder);

        mReadingNote = (TextView) findViewById(R.id.details_reading_note);
        mReadingNoteCard = (CardView) findViewById(R.id.details_reading_note_card);

        mMeaningNote = (TextView) findViewById(R.id.details_meaning_note);
        mMeaningNoteCard = (CardView) findViewById(R.id.details_meaning_note_card);

        mLocked = (LinearLayout) findViewById(R.id.details_progress_locked);

        mBurned = (LinearLayout) findViewById(R.id.details_progress_burned);
        mBurnedDate = (TextView) findViewById(R.id.details_progress_burned_date);
        mProgressContent = (LinearLayout) findViewById(R.id.details_progress_content);

        mProgressCard = (CardView) findViewById(R.id.details_progress_card);

        mSRSLogo = (ImageView) findViewById(R.id.details_progress_srs_logo);
        mSRSLevel = (TextView) findViewById(R.id.details_progress_srs_level);
        mUnlockHolder = (LinearLayout) findViewById(R.id.details_progress_unlocked_holder);
        mUnlocked = (TextView) findViewById(R.id.details_progress_unlocked);
        mNextAvailableHolder = (LinearLayout) findViewById(R.id.details_progress_next_available_holder);
        mNextAvailable = (RelativeTimeTextView) findViewById(R.id.details_progress_next_available);
        mMeaningCorrectHolder = (RelativeLayout) findViewById(R.id.details_progress_meaning_correct_holder);
        mMeaningCorrectPercentage = (TextView) findViewById(R.id.details_progress_meaning_correct_percentage);
        mMeaningIncorrectHolder = (RelativeLayout) findViewById(R.id.details_progress_meaning_incorrect_holder);
        mMeaningIncorrectPercentage = (TextView) findViewById(R.id.details_progress_meaning_incorrect_percentage);
        mMeaningMaxStreakHolder = (LinearLayout) findViewById(R.id.details_progress_meaning_max_streak_holder);
        mMeaningMaxStreak = (TextView) findViewById(R.id.details_progress_meaning_max_streak);
        mMeaningCurrentStreakHolder = (LinearLayout) findViewById(R.id.details_progress_meaning_current_streak_holder);
        mMeaningCurrentStreak = (TextView) findViewById(R.id.details_progress_meaning_current_streak);
        mReadingCorrectHolder = (RelativeLayout) findViewById(R.id.details_progress_reading_correct_holder);
        mReadingCorrectPercentage = (TextView) findViewById(R.id.details_progress_reading_correct_percentage);
        mReadingIncorrectHolder = (RelativeLayout) findViewById(R.id.details_progress_reading_incorrect_holder);
        mReadingIncorrectPercentage = (TextView) findViewById(R.id.details_progress_reading_incorrect_percentage);
        mReadingMaxStreakHolder = (LinearLayout) findViewById(R.id.details_progress_reading_max_streak_holder);
        mReadingMaxStreak = (TextView) findViewById(R.id.details_progress_reading_max_streak);
        mReadingCurrentStreakHolder = (LinearLayout) findViewById(R.id.details_progress_reading_current_streak_holder);
        mReadingCurrentStreak = (TextView) findViewById(R.id.details_progress_reading_current_streak);

        mMeaningCorrectProgressBar = (ProgressBar) findViewById(R.id.details_progress_meaning_correct_progress);
        mMeaningIncorrectProgressBar = (ProgressBar) findViewById(R.id.details_progress_meaning_incorrect_progress);
        mReadingCorrectProgressBar = (ProgressBar) findViewById(R.id.details_progress_reading_correct_progress);
        mReadingIncorrectProgressBar = (ProgressBar) findViewById(R.id.details_progress_reading_incorrect_progress);

        mReading.setTypeface(new Fonts().getKanjiFont(this));
        mOnyomi.setTypeface(new Fonts().getKanjiFont(this));
        mKunyomi.setTypeface(new Fonts().getKanjiFont(this));

        switch (mItem.getType()) {
            case RADICAL:
                switchToRadicalMode();
                break;
            case KANJI:
                switchToKanjiMode();
                break;
            case VOCABULARY:
                switchToVocabularyMode();
                break;
        }

        if (mItem instanceof UnlockItem || mItem instanceof CriticalItem) {
            load(mItem.getType().toString(), mItem.getLevel(), mItem.getCharacter(), mItem.getImage());
        } else {
            if (mItem != null)
                loadData();
            else {
                Log.e(TAG, "No item object passed; exiting...");
                Toast.makeText(getApplicationContext(), R.string.error_couldnt_load_data, Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    @Override
    public boolean onSupportNavigateUp() {
        super.onBackPressed();
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.item_details, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        switch (id) {
            case R.id.action_details_view_on_web:
                Intent intent = new Intent(this, Browser.class);
                intent.putExtra(Browser.ARG_ACTION, Browser.ACTION_ITEM_DETAILS);

                if (mItem.getType().equals(BaseItem.ItemType.RADICAL)) {
                    intent.putExtra(Browser.ARG_ITEM_TYPE, BaseItem.ItemType.RADICAL);
                    intent.putExtra(Browser.ARG_ITEM, mMeaning.getText().toString());
                }
                if (mItem.getType().equals(BaseItem.ItemType.KANJI)) {
                    intent.putExtra(Browser.ARG_ITEM_TYPE, BaseItem.ItemType.KANJI);
                    intent.putExtra(Browser.ARG_ITEM, mTitle.getText().toString());
                }
                if (mItem.getType().equals(BaseItem.ItemType.VOCABULARY)) {
                    intent.putExtra(Browser.ARG_ITEM_TYPE, BaseItem.ItemType.VOCABULARY);
                    intent.putExtra(Browser.ARG_ITEM, mTitle.getText().toString());
                }

                startActivity(intent);
        }
        return super.onOptionsItemSelected(item);
    }

    // TODO: ...
    private void setUpScrollView() {
        mScrollView = (ObservableScrollView) findViewById(R.id.details_scrollview);
        mScrollView.setCallbacks(new ObservableScrollView.Callbacks() {
            @Override
            public void onScrollChanged(int x, int y, int oldx, int oldy) {
                if (y <= 0) {
                    mToolbar.setAlpha(1f);
                } else if (y <= mToolbarHeight) {
                    mToolbar.setAlpha(1 - (1 * (y / (float) mToolbarHeight)));
                } else {
                    mToolbar.setAlpha(0f);
                }
            }
        });
    }

    private void setUpActionBar() {
        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);

        ActionBar mActionBar = getSupportActionBar();
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mActionBar.setDisplayShowTitleEnabled(false);

        mToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mToolbarHeight = mToolbar.getHeight();
            }
        });
    }

    private void switchToRadicalMode() {
        mAlternativeMeaningsHolder.setVisibility(View.GONE);
        mReadingsCard.setVisibility(View.GONE);
        mReadingNoteCard.setVisibility(View.GONE);
        mReadingCorrectHolder.setVisibility(View.GONE);
        mReadingIncorrectHolder.setVisibility(View.GONE);
        mReadingMaxStreakHolder.setVisibility(View.GONE);
        mReadingCurrentStreakHolder.setVisibility(View.GONE);
    }

    private void switchToKanjiMode() {
        mReadingHolder.setVisibility(View.GONE);
    }

    private void switchToVocabularyMode() {
        mOnyomiHolder.setVisibility(View.GONE);
        mKunyomiHolder.setVisibility(View.GONE);
    }

    private void loadData() {
        /** Title */
        if (mItem.getImage() != null) {
            mTitleImage.setVisibility(View.VISIBLE);
            Picasso.with(getApplicationContext()).load(mItem.getImage()).into(mTitleImage);
            mTitleImage.setColorFilter(getResources().getColor(android.R.color.white),
                    PorterDuff.Mode.SRC_ATOP);
            mTitle.setVisibility(View.GONE);
        } else {
            mTitleImage.setVisibility(View.GONE);
            mTitle.setText(mItem.getCharacter());
            mTitle.setTypeface(new Fonts().getKanjiFont(this));
            mTitle.setVisibility(View.VISIBLE);
        }

        /** Reading (Kana) */
        if (mItem.getType().equals(BaseItem.ItemType.VOCABULARY))
            mReading.setText(mItem.getKana());

        /** Meaning */
        String[] meanings = mItem.getMeaning().split(", ");
        mMeaning.setText(WordUtils.capitalize(meanings[0]));

        if (meanings.length > 1) {
            mAlternativeMeanings.setText("");
            for (int i = 0; i < meanings.length; i++) {
                if (i != 0) {
                    if (i == 1)
                        mAlternativeMeanings.setText(WordUtils.capitalize(meanings[i]));
                    else
                        mAlternativeMeanings.setText(mAlternativeMeanings.getText().toString()
                                + ", " + WordUtils.capitalize(meanings[i]));
                }
            }
        } else
            mAlternativeMeaningsHolder.setVisibility(View.GONE);

        /** Kanji readings */
        if (mItem.getType().equals(BaseItem.ItemType.KANJI)) {
            if (mItem.getOnyomi() != null)
                mOnyomi.setText(mItem.getOnyomi());
            else
                mOnyomiHolder.setVisibility(View.GONE);

            if (mItem.getKunyomi() != null)
                mKunyomi.setText(mItem.getKunyomi());
            else
                mKunyomiHolder.setVisibility(View.GONE);

            if (mItem.getImportantReading().equals("onyomi"))
                mKunyomi.setTextColor(getResources().getColor(R.color.text_gray_light));
            else
                mOnyomi.setTextColor(getResources().getColor(R.color.text_gray_light));
        }

        /** Level */
        mLevel.setText(mItem.getLevel() + "");

        /** User specific info */
        if (mItem.isUnlocked())
            showUserSpecific();
        else
            hideUserSpecific();

        mFlipper.setDisplayedChild(FLIPPER_CONTENT);
    }

    private void showUserSpecific() {
        /** Check whether burned and display content accordingly */
        if (mItem.isBurned()) {
            mBurned.setVisibility(View.VISIBLE);
            SimpleDateFormat sdf = new SimpleDateFormat("MMMM d, yyyy");
            mBurnedDate.setText(sdf.format(mItem.getBurnedDate()));
        }

        /** SRS Level */
        if (mItem.getSrsLevel().equals("apprentice")) {
            mSRSLogo.setImageResource(R.drawable.apprentice);
            mSRSLevel.setText(R.string.srs_title_apprentice);
        }
        if (mItem.getSrsLevel().equals("guru")) {
            mSRSLogo.setImageResource(R.drawable.guru);
            mSRSLevel.setText(R.string.srs_title_guru);
        }
        if (mItem.getSrsLevel().equals("master")) {
            mSRSLogo.setImageResource(R.drawable.master);
            mSRSLevel.setText(R.string.srs_title_master);
        }
        if (mItem.getSrsLevel().equals("enlighten")) {
            mSRSLogo.setImageResource(R.drawable.enlighten);
            mSRSLevel.setText(R.string.srs_title_enlightened);
        }
        if (mItem.getSrsLevel().equals("burned")) {
            mSRSLogo.setImageResource(R.drawable.burned);
            mSRSLevel.setText(R.string.srs_title_burned);
        }

        /** Unlock date */
        SimpleDateFormat unlockDateFormat = new SimpleDateFormat("MMMM d, yyyy");
        mUnlocked.setText(unlockDateFormat.format(mItem.getUnlockDate()) + "");

        /** Next available */
        SimpleDateFormat availableDateFormat = new SimpleDateFormat("dd MMMM HH:mm");

        if (PrefManager.isUseSpecificDates()) {
            mNextAvailable.setText(availableDateFormat.format(mItem.getAvailableDate()) + "");
        } else {
            mNextAvailable.setReferenceTime(mItem.getAvailableDate());
        }
        mNextAvailableHolder.setVisibility(mItem.isBurned() ? View.GONE : View.VISIBLE);

        /** Meaning */
        mMeaningCorrectPercentage.setText(mItem.getMeaningCorrectPercentage() + "");
        mMeaningIncorrectPercentage.setText(mItem.getMeaningIncorrectPercentage() + "");

        mMeaningCorrectProgressBar.setProgress(mItem.getMeaningCorrectPercentage());
        mMeaningIncorrectProgressBar.setProgress(mItem.getMeaningIncorrectPercentage());

        mMeaningMaxStreak.setText(mItem.getMeaningMaxStreak() + "");
        mMeaningCurrentStreak.setText(mItem.getMeaningCurrentStreak() + "");

        /** Reading (For Kanji and Vocabulary) */
        if (mItem.getType().equals(BaseItem.ItemType.KANJI) || mItem.getType().equals(BaseItem.ItemType.VOCABULARY)) {
            mReadingCorrectPercentage.setText(mItem.getReadingCorrectPercentage() + "");
            mReadingIncorrectPercentage.setText(mItem.getReadingIncorrectPercentage() + "");

            mReadingMaxStreak.setText(mItem.getReadingMaxStreak() + "");
            mReadingCurrentStreak.setText(mItem.getReadingCurrentStreak() + "");

            mReadingCorrectProgressBar.setProgress(mItem.getReadingCorrectPercentage());
            mReadingIncorrectProgressBar.setProgress(mItem.getReadingIncorrectPercentage());
        }

        /** User synonyms */
        if (mItem.getUserSynonyms() != null) {
            String synonyms = WordUtils.capitalize(
                    Arrays.toString(mItem.getUserSynonyms()).replace("[", "").replace("]", ""));
            mUserSynonyms.setText(synonyms);
        } else {
            if (mAlternativeMeaningsHolder.getVisibility() == View.GONE)
                mAlternativeMeaningsAndUserSynonymsCard.setVisibility(View.GONE);
            else
                mUserSynonymsHolder.setVisibility(View.GONE);
        }

        /** Reading note */
        if (mItem.getReadingNote() != null)
            mReadingNote.setText(mItem.getReadingNote());
        else
            mReadingNoteCard.setVisibility(View.GONE);

        /** Meaning note */
        if (mItem.getMeaningNote() != null)
            mMeaningNote.setText(mItem.getMeaningNote());
        else
            mMeaningNoteCard.setVisibility(View.GONE);
    }

    private void hideUserSpecific() {
        if (mAlternativeMeaningsHolder.getVisibility() == View.GONE)
            mAlternativeMeaningsAndUserSynonymsCard.setVisibility(View.GONE);
        else
            mUserSynonymsHolder.setVisibility(View.GONE);

        mMeaningNoteCard.setVisibility(View.GONE);
        mReadingNoteCard.setVisibility(View.GONE);
        mProgressContent.setVisibility(View.GONE);
        mLocked.setVisibility(View.VISIBLE);
    }

    private void load(String type, final int level, final String character, final String image) {
        if (mFlipper != null) {
            mFlipper.setDisplayedChild(FLIPPER_PROGRESS_BAR);
        }

        /** In case of a radical item */
        if (type.equals(BaseItem.TYPE_RADICAL)) {
            WaniKaniApi.getRadicalsList(level + "").enqueue(new ThroughDbCallback<Request<RadicalsList>, RadicalsList>() {
                @Override
                public void onResponse(Call<Request<RadicalsList>> call, Response<Request<RadicalsList>> response) {
                    super.onResponse(call, response);
                    if (response.isSuccessful() && response.body().requested_information != null) {
                        load(response.body().requested_information);
                    } else {
                        onFailure(call, null);
                    }
                }

                @Override
                public void onFailure(Call<Request<RadicalsList>> call, Throwable t) {
                    super.onFailure(call, t);

                    RadicalsList list = new RadicalsList();
                    list.addAll(DatabaseManager.getItems(BaseItem.ItemType.RADICAL, new int[]{level}));

                    if (list.size() != 0) {
                        load(list);
                    } else {
                        showErrorAndExit();
                    }
                }

                void load(ItemsList list) {
                    if (character != null) {
                        for (BaseItem item : list) {
                            if (item.getCharacter() != null) {
                                if (item.getCharacter().equals(character)) {
                                    mItem = item;
                                    loadData();
                                    break;
                                }
                            }
                        }
                    } else {
                        for (BaseItem item : list) {
                            if (item.getImage() != null) {
                                if (item.getImage().equals(image)) {
                                    mItem = item;
                                    loadData();
                                    break;
                                }
                            }
                        }
                    }
                }
            });
        }

        /** In case of a Kanji item */
        if (type.equals(BaseItem.TYPE_KANJI)) {
            WaniKaniApi.getKanjiList(level + "").enqueue(new ThroughDbCallback<Request<KanjiList>, KanjiList>() {
                @Override
                public void onResponse(Call<Request<KanjiList>> call, Response<Request<KanjiList>> response) {
                    super.onResponse(call, response);
                    if (response.isSuccessful() && response.body().requested_information != null) {
                        load(response.body().requested_information);
                    } else {
                        onFailure(call, null);
                    }
                }

                @Override
                public void onFailure(Call<Request<KanjiList>> call, Throwable t) {
                    super.onFailure(call, t);

                    KanjiList list = new KanjiList();
                    list.addAll(DatabaseManager.getItems(BaseItem.ItemType.KANJI, new int[]{level}));

                    if (list.size() != 0) {
                        load(list);
                    } else {
                        showErrorAndExit();
                    }
                }

                void load(ItemsList list) {
                    for (BaseItem item : list) {
                        if (item.getCharacter().equals(character)) {
                            mItem = item;
                            loadData();
                            break;
                        }
                    }
                }
            });
        }

        /** In case of a vocabulary item */
        WaniKaniApi.getVocabularyList(level + "").enqueue(new ThroughDbCallback<Request<VocabularyList>, VocabularyList>() {
            @Override
            public void onResponse(Call<Request<VocabularyList>> call, Response<Request<VocabularyList>> response) {
                super.onResponse(call, response);
                if (response.isSuccessful() && response.body().requested_information != null) {
                    load(response.body().requested_information);
                } else {
                    onFailure(call, null);
                }
            }

            @Override
            public void onFailure(Call<Request<VocabularyList>> call, Throwable t) {
                super.onFailure(call, t);

                VocabularyList list = new VocabularyList();
                list.addAll(DatabaseManager.getItems(BaseItem.ItemType.VOCABULARY, new int[]{level}));

                if (list.size() != 0) {
                    load(list);
                } else {
                    showErrorAndExit();
                }
            }

            void load(ItemsList list) {
                for (BaseItem item : list) {
                    if (item.getCharacter().equals(character)) {
                        mItem = item;
                        loadData();
                        break;
                    }
                }
            }
        });
    }

    private void showErrorAndExit() {
        Log.e(TAG, "Failed to fetch item info; exiting...");
        Toast.makeText(getApplicationContext(), R.string.error_couldnt_load_data, Toast.LENGTH_SHORT).show();
        finish();
    }
} 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 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);
        }
    }
} public class LegendDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.content_radicals_legend)
                .setView(R.layout.dialog_legend)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dismiss();
                        PrefManager.setLegendLearned(true);
                    }
                })
                .create();
    }
} public class LevelPickerDialogFragment extends DialogFragment implements LevelPickerCheckBoxAdapter.LevelPickerCheckBoxAdapterListener {

    private static final String ARG_USER_LEVEL = "user_level";
    private static final String ARG_FRAGMENT_ID = "fragment_id";
    private static final String ARG_SELECTED_ITEMS_STORAGE = "selected_items_storage";
    private static final String ARG_SELECTION_STORAGE = "selection_storage";

    private LevelDialogListener mListener;
    private String userLevel;

    private int fragmentId;

    private String selectedLevel = "0";
    private int wanikaniLevelsNumber;

    private Set<Integer> mSelectedItems;
    private ArrayList<Integer> mSelectedItemsStorage;

    private boolean[] mSelection;
    private boolean[] mSelectionStorage;

    public void init(int fragmentId, String userLevel) {
        this.userLevel = userLevel;
        this.fragmentId = fragmentId;
    }

    @Override
    public Dialog onCreateDialog(Bundle bundle) {
        final Context context = getActivity();
        wanikaniLevelsNumber = context.getResources().getInteger(R.integer.wanikani_levels_number);

        if (bundle != null) {
            userLevel = bundle.getString(ARG_USER_LEVEL);
            fragmentId = bundle.getInt(ARG_FRAGMENT_ID);
            mSelectedItemsStorage = bundle.getIntegerArrayList(ARG_SELECTED_ITEMS_STORAGE);
            mSelectionStorage = bundle.getBooleanArray(ARG_SELECTION_STORAGE);
        }

        mListener = (LevelDialogListener) getFragmentManager().findFragmentById(fragmentId);

        mSelectedItems = new HashSet<>();
        mSelection = new boolean[wanikaniLevelsNumber];

        if (mSelectedItemsStorage == null && mSelectionStorage == null) {
            mSelectedItems.add(Integer.parseInt(userLevel));
            mSelection[Integer.parseInt(userLevel) - 1] = true;
        }

        if (mSelectedItemsStorage != null) {
            mSelectedItems.clear();
            for (int i = 0; i < mSelectedItemsStorage.size(); ++i) {
                mSelectedItems.add(mSelectedItemsStorage.get(i));
            }
        }

        if (mSelectionStorage != null) {
            for (int i = 0; i < mSelectionStorage.length; ++i) {
                mSelection[i] = mSelectionStorage[i];
            }
        }

        LayoutInflater inflater = getActivity().getLayoutInflater();
        ListView checkList = (ListView) inflater.inflate(R.layout.dialog_level_picker, null);

        ArrayList<LevelPickerCheckBoxAdapter.LevelCheckBox> mListItems = new ArrayList<>();

        for (int i = 0; i < wanikaniLevelsNumber; i++) {
            LevelPickerCheckBoxAdapter.LevelCheckBox checkBox =
                    new LevelPickerCheckBoxAdapter.LevelCheckBox(String.valueOf(i + 1), mSelection[i]);
            mListItems.add(checkBox);
        }

        checkList.setAdapter(new LevelPickerCheckBoxAdapter(context,
                R.layout.item_level_picker_checkbox, mListItems, this));

        return new AlertDialog.Builder(context)
                .setTitle(R.string.card_content_progress_level)
                .setView(checkList)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        selectedLevel = "0";

                        for (Integer item : mSelectedItems) {
                            if (selectedLevel.equals("0")) {
                                selectedLevel = String.valueOf(item + 1);
                            } else {
                                selectedLevel += "," + (item + 1);
                            }
                        }

                        if (!selectedLevel.equals("0")) {
                            mListener.onLevelDialogPositiveClick(LevelPickerDialogFragment.this, selectedLevel);

                            if (mSelectedItemsStorage == null) {
                                mSelectedItemsStorage = new ArrayList<>();
                            }

                            if (mSelectionStorage == null) {
                                mSelectionStorage = new boolean[wanikaniLevelsNumber];
                            }

                            mSelectedItemsStorage.clear();

                            for (int item : mSelectedItems) {
                                mSelectedItemsStorage.add(item);
                            }

                            for (int i = 0; i < mSelection.length; i++) {
                                mSelectionStorage[i] = mSelection[i];
                            }

                            dismiss();
                        } else {
                            Toast.makeText(context, R.string.error_no_levels_selected, Toast.LENGTH_LONG).show();
                        }
                    }
                })
                .setNegativeButton(R.string.reset, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (mSelectedItems != null) {
                            mSelectedItems.clear();
                        }
                        if (mSelection != null) {
                            mSelection = new boolean[wanikaniLevelsNumber];
                        }
                        if (mSelectedItemsStorage != null) {
                            mSelectedItemsStorage.clear();
                        }
                        if (mSelectionStorage != null) {
                            mSelectionStorage = new boolean[wanikaniLevelsNumber];
                        }

                        mListener.onLevelDialogResetClick(LevelPickerDialogFragment.this, userLevel);

                        dismiss();
                    }
                })
                .setNeutralButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dismiss();
                    }
                })
                .create();
    }

    @Override
    public void onLevelPickerCheckBoxAdapterItemClickListener(int which, boolean isChecked) {
        if (isChecked) {
            mSelectedItems.add(which);
        } else if (mSelectedItems.contains(which)) {
            mSelectedItems.remove(Integer.valueOf(which));
        }

        mSelection[which] = isChecked;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(ARG_USER_LEVEL, userLevel);
        outState.putInt(ARG_FRAGMENT_ID, fragmentId);
        outState.putIntegerArrayList(ARG_SELECTED_ITEMS_STORAGE, mSelectedItemsStorage);
        outState.putBooleanArray(ARG_SELECTION_STORAGE, mSelectionStorage);
        super.onSaveInstanceState(outState);
    }

    public interface LevelDialogListener {
        void onLevelDialogPositiveClick(DialogFragment dialog, String level);

        void onLevelDialogResetClick(DialogFragment dialog, String level);
    }
} @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 BaseItem implements Serializable {
    public static final String TYPE_RADICAL = "radical";
    public static final String TYPE_KANJI = "kanji";
    public static final String TYPE_VOCABULARY = "vocabulary";

    int id;

    String character;
    String kana;
    String meaning;
    String image;
    String onyomi;
    String kunyomi;
    String important_reading;
    int level;
    UserSpecific user_specific;

    ItemType typeEnum;

    public BaseItem() {
    }

    public BaseItem(int id, String character, String kana, String meaning, String image, String onyomi,
                    String kunyomi, String importantReading, int level, String type, String srs,
                    long unlockDate, long availableDate, boolean burned, long burnedDate,
                    int meaningCorrect, int meaningIncorrect, int meaningMaxStreak,
                    int meaningCurrentStreak, int readingCorrect, int readingIncorrect,
                    int readingMaxStreak, int readingCurrentStreak, String meaningNote,
                    String[] userSynonyms, String readingNote) {
        this.id = id;
        this.character = character;
        this.kana = kana;
        this.meaning = meaning;
        this.image = image;
        this.onyomi = onyomi;
        this.kunyomi = kunyomi;
        this.important_reading = importantReading;
        this.level = level;
        this.typeEnum = ItemType.fromString(type);

        if (unlockDate != 0)
            this.user_specific = new UserSpecific(
                    srs,
                    unlockDate,
                    availableDate,
                    burned,
                    burnedDate,
                    meaningCorrect,
                    meaningIncorrect,
                    meaningMaxStreak,
                    meaningCurrentStreak,
                    readingCorrect,
                    readingIncorrect,
                    readingMaxStreak,
                    readingCurrentStreak,
                    meaningNote,
                    userSynonyms,
                    readingNote
            );
    }

    public int getId() {
        return id;
    }

    public ItemType getType() {
        return typeEnum;
    }

    public void setType(ItemType type) {
        this.typeEnum = type;
    }

    public String getCharacter() {
        return character;
    }

    public String getKana() {
        return getType() == ItemType.VOCABULARY ? kana : null;
    }

    public String getMeaning() {
        return meaning;
    }

    public String getOnyomi() {
        return getType() == ItemType.KANJI ? onyomi : null;
    }

    public String getKunyomi() {
        return getType() == ItemType.KANJI ? kunyomi : null;
    }

    public String getImportantReading() {
        return getType() == ItemType.KANJI ? important_reading : null;
    }

    public String getImage() {
        return image;
    }

    public int getLevel() {
        return level;
    }

    public boolean isUnlocked() {
        return user_specific != null;
    }

    public String getSrsLevel() {
        return isUnlocked() ? user_specific.srs : null;
    }

    public long getUnlockDate() {
        return isUnlocked() ? user_specific.unlocked_date * 1000 : 0;
    }

    public long getUnlockDateInSeconds() {
        return isUnlocked() ? user_specific.unlocked_date : 0;
    }

    public long getAvailableDate() {
        return isUnlocked() ? user_specific.available_date * 1000 : 0;
    }

    public long getAvailableDateInSeconds() {
        return isUnlocked() ? user_specific.available_date : 0;
    }

    public boolean isBurned() {
        return isUnlocked() && user_specific.burned;
    }

    public long getBurnedDate() {
        return isUnlocked() ? user_specific.burned_date * 1000 : 0;
    }

    public long getBurnedDateInSeconds() {
        return isUnlocked() ? user_specific.burned_date : 0;
    }

    public int getMeaningCorrect() {
        return isUnlocked() ? user_specific.meaning_correct : 0;
    }

    public int getMeaningIncorrect() {
        return isUnlocked() ? user_specific.meaning_incorrect : 0;
    }

    public int getMeaningMaxStreak() {
        return isUnlocked() ? user_specific.meaning_max_streak : 0;
    }

    public int getMeaningCurrentStreak() {
        return isUnlocked() ? user_specific.meaning_current_streak : 0;
    }

    public int getMeaningAnswersCount() {
        return isUnlocked() ? user_specific.meaning_correct + user_specific.meaning_incorrect : 0;
    }

    public int getMeaningCorrectPercentage() {
        return isUnlocked() ? (int) ((double) user_specific.meaning_correct / getMeaningAnswersCount() * 100) : 0;
    }

    public int getMeaningIncorrectPercentage() {
        return isUnlocked() ? (int) ((double) user_specific.meaning_incorrect / getMeaningAnswersCount() * 100) : 0;
    }

    public int getReadingCorrect() {
        return isUnlocked() ? getType() == ItemType.KANJI ? user_specific.reading_correct : 0 : 0;
    }

    public int getReadingIncorrect() {
        return isUnlocked() ? getType() == ItemType.KANJI ? user_specific.reading_incorrect : 0 : 0;
    }

    public int getReadingMaxStreak() {
        return isUnlocked() ? getType() == ItemType.KANJI ? user_specific.reading_max_streak : 0 : 0;
    }

    public int getReadingCurrentStreak() {
        return isUnlocked() ? getType() == ItemType.KANJI ? user_specific.reading_current_streak : 0 : 0;
    }

    public int getReadingAnswersCount() {
        return isUnlocked() ? getType() == ItemType.KANJI ?
                user_specific.reading_correct + user_specific.reading_incorrect : 0 : 0;
    }

    public int getReadingCorrectPercentage() {
        return isUnlocked() ? getType() == ItemType.KANJI ?
                (int) ((double) user_specific.reading_correct / getReadingAnswersCount() * 100) : 0 : 0;
    }

    public int getReadingIncorrectPercentage() {
        return isUnlocked() ? getType() == ItemType.KANJI ?
                (int) ((double) user_specific.reading_incorrect / getReadingAnswersCount() * 100) : 0 : 0;
    }

    public String getMeaningNote() {
        return isUnlocked() ? user_specific.meaning_note : null;
    }

    public String[] getUserSynonyms() {
        return isUnlocked() ? user_specific.user_synonyms : null;
    }

    public String getUserSynonymsAsString() {
        if (getUserSynonyms() == null)
            return null;

        String synonyms = "";
        for (int i = 0; i < getUserSynonyms().length; i++)
            if (i == 0)
                synonyms = getUserSynonyms()[i];
            else
                synonyms += ", " + getUserSynonyms()[i];
        return synonyms;
    }

    public String getReadingNote() {
        return isUnlocked() ? getType() == ItemType.KANJI ? user_specific.reading_note : null : null;
    }

    public enum ItemType implements Serializable {
        RADICAL, KANJI, VOCABULARY;

        public static ItemType fromString(String type) {
            if (type.equals(TYPE_RADICAL))
                return ItemType.RADICAL;
            if (type.equals(TYPE_KANJI))
                return ItemType.KANJI;
            if (type.equals(TYPE_VOCABULARY))
                return ItemType.VOCABULARY;
            else return null;
        }

        public String toString() {
            switch (this) {
                case RADICAL:
                    return TYPE_RADICAL;
                case KANJI:
                    return TYPE_KANJI;
                case VOCABULARY:
                    return TYPE_VOCABULARY;
                default:
                    return null;
            }
        }
    }

    class UserSpecific implements Serializable {
        private String srs;
        private long unlocked_date;
        private long available_date;
        private boolean burned;
        private long burned_date;
        private int meaning_correct;
        private int meaning_incorrect;
        private int meaning_max_streak;
        private int meaning_current_streak;
        private int reading_correct;
        private int reading_incorrect;
        private int reading_max_streak;
        private int reading_current_streak;
        private String meaning_note;
        private String[] user_synonyms;
        private String reading_note;

        public UserSpecific(String srs, long unlockDate, long availableDate, boolean burned, long burnedDate,
                            int meaningCorrect, int meaningIncorrect, int meaningMaxStreak,
                            int meaningCurrentStreak, int readingCorrect, int readingIncorrect,
                            int readingMaxStreak, int readingCurrentStreak, String meaningNote,
                            String[] userSynonyms, String readingNote) {
            this.srs = srs;
            this.unlocked_date = unlockDate;
            this.available_date = availableDate;
            this.burned = burned;
            this.burned_date = burnedDate;
            this.meaning_correct = meaningCorrect;
            this.meaning_incorrect = meaningIncorrect;
            this.meaning_max_streak = meaningMaxStreak;
            this.meaning_current_streak = meaningCurrentStreak;
            this.reading_correct = readingCorrect;
            this.reading_incorrect = readingIncorrect;
            this.reading_max_streak = readingMaxStreak;
            this.reading_current_streak = readingCurrentStreak;
            this.meaning_note = meaningNote;
            this.user_synonyms = userSynonyms;
            this.reading_note = readingNote;
        }
    }
} public class ItemsList extends ArrayList<BaseItem> implements Storable {
    @Override
    public void save() {
        if (size() == 0) return;

        DatabaseManager.saveItems(this, getType());
    }

    protected BaseItem.ItemType getType() {
        return BaseItem.ItemType.RADICAL;
    }

    @Override
    public boolean add(BaseItem item) {
        item.setType(getType());
        return super.add(item);
    }

    @Override
    public void add(int index, BaseItem item) {
        item.setType(getType());
        super.add(index, item);
    }

    @Override
    public boolean addAll(Collection<? extends BaseItem> c) {
        if (c == null) return false;

        for (BaseItem item : c) {
            item.setType(getType());
        }

        return super.addAll(c);
    }
} public class RadicalsList extends ItemsList {

    @Override
    protected BaseItem.ItemType getType() {
        return BaseItem.ItemType.RADICAL;
    }
} public class Request<T> {
    public User user_information;
    public T requested_information;
    public Error error;
} 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.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewFlipper;
import com.tonicartos.widget.stickygridheaders.StickyGridHeadersGridView;
import java.util.Collections;
import java.util.Comparator;
import retrofit2.Call;
import retrofit2.Response;
import tr.xip.wanikani.R;
import tr.xip.wanikani.app.activity.ItemDetailsActivity;
import tr.xip.wanikani.client.WaniKaniApi;
import tr.xip.wanikani.client.task.callback.ThroughDbCallback;
import tr.xip.wanikani.database.DatabaseManager;
import tr.xip.wanikani.dialogs.LegendDialogFragment;
import tr.xip.wanikani.dialogs.LevelPickerDialogFragment;
import tr.xip.wanikani.managers.PrefManager;
import tr.xip.wanikani.models.BaseItem;
import tr.xip.wanikani.models.ItemsList;
import tr.xip.wanikani.models.RadicalsList;
import tr.xip.wanikani.models.Request;
import tr.xip.wanikani.models.User;
import tr.xip.wanikani.utils.Utils;
import tr.xip.wanikani.widget.adapter.RadicalsAdapter;
package tr.xip.wanikani.app.fragment;





public class RadicalsFragment extends Fragment implements LevelPickerDialogFragment.LevelDialogListener,
        SwipeRefreshLayout.OnRefreshListener {

    Context context;

    TextView mMessageTitle;
    TextView mMessageSummary;
    ImageView mMessageIcon;
    ViewFlipper mMessageFlipper;

    StickyGridHeadersGridView mGrid;

    ViewFlipper mListFlipper;

    LevelPickerDialogFragment mLevelPickerDialog;

    RadicalsAdapter mRadicalsAdapter;

    View rootView;

    String level = "";

    MenuItem mLevelItem;

    private SwipeRefreshLayout mMessageSwipeRefreshLayout;

    private void showLegend() {
        new LegendDialogFragment().show(getActivity().getSupportFragmentManager(), "legend-dialog");