public class MAVApplication extends MultiDexApplication {

    static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    private static MAVApplication application = null;

    private RefWatcher refWatcher;
    private BoxStore boxStore;
    private DataRepository mDataRepository;
    private SharedPreferences mPrefs;

    public static MAVApplication getInstance() {
        return application;
    }

    public static File getCacheDirectory() {
        return application.getCacheDir();
    }

    public static RefWatcher getRefWatcher(Context context) {
        MAVApplication application = (MAVApplication) context.getApplicationContext();
        return application.refWatcher;
    }

    public BoxStore getBoxStore() {
        return boxStore;
    }

    public DataRepository getRepository() {
        return mDataRepository;
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();

        application = this;

        //ACRA.init(this);
        initializeLeakCanary();
        initializeTimber();
        initializeObjectBox();
        ImageLoaderManager.getInstance(application);
        mDataRepository = new ObjectBoxRepository();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        setupDataPaths();
        ParseManager.getInstance(getApplicationContext());
    }

    private void initializeObjectBox() {
        boxStore = MyObjectBox.builder().androidContext(this).build();
    }

    private void initializeLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        refWatcher = LeakCanary.install(this);
    }

    private void initializeTimber() {
        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree() {
                // Add the line number to the tag
                @Override
                protected String createStackElementTag(StackTraceElement element) {
                    return String.format("[Line - %s] [Method - %s] [Class - %s]",
                            element.getLineNumber(),
                            element.getMethodName(),
                            super.createStackElementTag(element));
                }
            });
        } else {
            Timber.plant(new ReleaseTree());
        }
    }

    private void setupDataPaths() {
        String dataDirPath = mPrefs.getString(Constants.KEY_LIBRARY_PATH, "");
        File file = null;
        if (TextUtils.isEmpty(dataDirPath)) {
            file = new File(mPrefs.getBoolean(Constants.KEY_LIBRARY_FORCE2SD, false) ?
                    StorageUtils.getExtSDCard(this) : Environment.getExternalStorageDirectory(),
                    StorageUtils.DEFAULT_LIBRARY_FOLDER);
            file.mkdirs();
            dataDirPath = file.getAbsolutePath();
            mPrefs.edit().putString(Constants.KEY_LIBRARY_PATH, dataDirPath).apply();
        }
        if (!TextUtils.isEmpty(dataDirPath))
            StorageUtils.setDataDirectory(new File(dataDirPath));
        if (StorageUtils.getDataDirectory() == null)
            StorageUtils.setDataDirectory(new File(mPrefs.getBoolean(Constants.KEY_LIBRARY_FORCE2SD, false) ?
                    StorageUtils.getExtSDCard(this) : Environment.getExternalStorageDirectory(), StorageUtils.DEFAULT_LIBRARY_FOLDER));

        if (StorageUtils.getCacheDir() == null) {
            StorageUtils.setCacheDir(new File(StorageUtils.getExtSDCard(this), StorageUtils.DEFAULT_LIBRARY_FOLDER + "Cache/"));
        }
    }

    public SharedPreferences getPreferences() {
        return mPrefs;
    }

    /**
     * A tree which logs important information for crash reporting.
     */
    private static class ReleaseTree extends Timber.Tree {

        private static final int MAX_LOG_LENGTH = 4000;

        @Override
        protected boolean isLoggable(int priority) {
            if (priority == Log.VERBOSE || priority == Log.DEBUG) {
                return false;
            }

            // Only log WARN, INFO, ERROR, WTF
            return true;
        }

        @Override
        protected void log(int priority, String tag, String message, Throwable t) {
            if (isLoggable(priority)) {
                // Message is short enough, does not need to be broken into chunks
                if (message.length() < MAX_LOG_LENGTH) {
                    if (priority == Log.ASSERT) {
                        Log.wtf(tag, message);
                    } else {
                        Log.println(priority, tag, message);
                    }
                    return;
                }

                // Split by line, then ensure each line can fit into Log's maximum length
                for (int i = 0, length = message.length(); i < length; i++) {
                    int newline = message.indexOf('\n', i);
                    newline = newline != -1 ? newline : length;
                    do {
                        int end = Math.min(newline, i + MAX_LOG_LENGTH);
                        String part = message.substring(i, end);
                        if (priority == Log.ASSERT) {
                            Log.wtf(tag, part);
                        } else {
                            Log.println(priority, tag, part);
                        }
                        i = end;
                    } while (i < newline);
                }
            }
        }
    }
} public class AnimeRecyclerAdapter extends FlexibleAdapter<AnimeRecyclerAdapter.ViewHolder, Anime> implements Filterable, FastScrollRecyclerView.SectionedAdapter {

    private static final String TAG = AnimeRecyclerAdapter.class.getSimpleName();
    private final int mLoaderType = 0;
    private GridRecyclerView mRecyclerView;
    private int mSectionType = 0;
    private HashMap<Long, FavoriteTag> mTagHashMap;

    public void sort(Comparator<Anime> comparator) {
        Collections.sort(mResults, comparator);
        notifyDataSetChanged();
    }

    public void setFilterType(int filterType) {
        mFilterType = filterType;
    }

    public Filter getFilter(boolean priorityCheck, boolean sourceCheck) {
        return mFilter = new AnimeCustomFilter(priorityCheck, sourceCheck);
    }

    @Override
    public Filter getFilter() {
        switch (mFilterType) {
            case 0:
                mFilter = new AnimeFilter();
                break;
            case 1:
                mFilter = new AnimeCustomFilter();
                break;
        }
        return mFilter;
    }

    public void setCatalogPositions(List<Anime> animeWithPositions) {
        if (animeWithPositions != null) {
            HashMap<String, Integer> positionMap = new HashMap<>();
            for (Anime anime : animeWithPositions) {
                //positionMap.put(anime.getUrl(), anime.getCatalogPosition());
            }
            for (Anime anime : mItems) {
                if (positionMap.containsKey(anime.getUrl())) {
                    //anime.setCatalogPosition(positionMap.get(anime.getUrl()));
                }
            }
        }
    }

    public int getCatalogType() {
        return mCatalogType;
    }

    public void setCatalogType(int mCatalogType) {
        this.mCatalogType = mCatalogType;
    }

    public List<Anime> getList() {
        return mResults;
    }

    public void setDisplayType(int displayType) {
        this.mDisplayType = displayType;
        //notifyDataSetChanged();
    }

    int mOriginalTextSize = -1;


    public void setSectionType(int type) {
        if (mOriginalTextSize == -1)
            mOriginalTextSize = mRecyclerView.getPopupTextSize();
        mSectionType = type;
        if (mSectionType == 2) {
            mRecyclerView.setPopupTextSize(DisplayUtils.dpToPx(24));
            mTagHashMap = new HashMap<>();
            for (FavoriteTag tag : MAVApplication.getInstance().getRepository().getFavoriteTags())
                mTagHashMap.put(tag.getId(), tag);
        } else {
            mRecyclerView.setPopupTextSize(mOriginalTextSize);
        }
    }

    @NonNull
    @Override
    public String getSectionName(int position) {
        switch (mSectionType) {
            case 1:
                return String.valueOf(getItem(position).getTitle().charAt(0));
            case 2:
                if (mTagHashMap.isEmpty() || mTagHashMap.get(getItem(position).getTagId()) == null) {
                    return String.valueOf((position + 1));
                }
                return String.valueOf(mTagHashMap.get(getItem(position).getTagId()).getTitle());
            default:
                return String.valueOf((position + 1));
        }
    }

    private Context mContext;
    List<Anime> mResults;
    private LayoutInflater mInflater;
    private OnItemClickListener mClickListener;
    //Selection fields
    private boolean
            mLastItemInActionMode = false,
            mSelectAll = false;

    private boolean mShowBadge = false;
    private HashMap<Long, Integer> mRibbonIDs = null;
    SharedPreferences mPrefs;

    private Filter mFilter;
    private int mFilterType = 0;
    private int mDisplayType = 0;
    private int mSecondaryType = -1;
    private int mCatalogType = -1;

    public static final int NONE = -1;
    public static final int GENRE = 0;
    public static final int CREATOR = 1;
    public static final int LATEST_EPISODE = 2;
    public static final int SOURCE = 3;
    public static final int SOURCE_AND_LANG = 4;

    public void setRecyclerView(GridRecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    public AnimeRecyclerAdapter(GridRecyclerView recyclerView, List<Anime> animeList, int displayType, Context context, boolean showBadge, OnItemClickListener listener) {
        this.mContext = context;
        this.mClickListener = listener;
        this.mShowBadge = showBadge;
        this.mDisplayType = displayType;
        this.mRecyclerView = recyclerView;
        mResults = new ArrayList<Anime>();
        checkAnime(animeList);
        //Collections.sort(animeList, Anime.Order.ByCatalogIndex);
        this.mItems = animeList;
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mReadColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastReadColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
        mRibbonIDs = new HashMap<>();
        for (FavoriteTag tag : MAVApplication.getInstance().getRepository().getFavoriteTags()) {
            mRibbonIDs.put(tag.getId(), tag.getRibbonID());
        }
    }

    public AnimeRecyclerAdapter(GridRecyclerView recyclerView, int catalogType, List<Anime> animeList, int displayType, Context context, boolean showBadge, OnItemClickListener listener) {
        this.mContext = context;
        this.mClickListener = listener;
        this.mShowBadge = showBadge;
        this.mDisplayType = displayType;
        this.mCatalogType = catalogType;
        this.mRecyclerView = recyclerView;
        if (animeList == null)
            animeList = new ArrayList<>();
        checkAnime(animeList);
        this.mItems = animeList;
        mResults = (List<Anime>) ((ArrayList<Anime>) mItems).clone();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mReadColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastReadColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
        mRibbonIDs = new HashMap<>();
        for (FavoriteTag tag : MAVApplication.getInstance().getRepository().getFavoriteTags()) {
            mRibbonIDs.put(tag.getId(), tag.getRibbonID());
        }
    }

    public AnimeRecyclerAdapter(List<Anime> animeList, int displayType, Context context, boolean showBadge, OnItemClickListener listener) {
        this.mContext = context;
        this.mClickListener = listener;
        this.mShowBadge = showBadge;
        this.mDisplayType = displayType;
        mResults = new ArrayList<Anime>();
        checkAnime(animeList);
        this.mItems = animeList;
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mReadColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastReadColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
        mRibbonIDs = new HashMap<>();
        for (FavoriteTag tag : MAVApplication.getInstance().getRepository().getFavoriteTags()) {
            mRibbonIDs.put(tag.getId(), tag.getRibbonID());
        }
    }

    public AnimeRecyclerAdapter(List<Anime> animeList, int displayType, int secondaryType, Context context, boolean showBadge, OnItemClickListener listener) {
        this.mContext = context;
        this.mClickListener = listener;
        this.mShowBadge = showBadge;
        this.mDisplayType = displayType;
        this.mSecondaryType = secondaryType;
        mResults = new ArrayList<Anime>();
        checkAnime(animeList);
        this.mItems = animeList;
        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        mReadColor = mPrefs.getInt(Constants.KEY_EPISODE_VIEWED_COLOR, Color.GRAY);
        mUnreadColor = mPrefs.getInt(Constants.KEY_EPISODE_UNVIEWED_COLOR, ThemeManager.getInstance(context).getTextColor());
        if (!ThemeManager.getInstance().isValidTextColor(mUnreadColor))
            mUnreadColor = ThemeManager.getInstance(context).getTextColor();
        mLastReadColor = mPrefs.getInt(Constants.KEY_EPISODE_LAST_VIEWED_COLOR, Color.RED);
        mRibbonIDs = new HashMap<>();
        for (FavoriteTag tag : MAVApplication.getInstance().getRepository().getFavoriteTags()) {
            mRibbonIDs.put(tag.getId(), tag.getRibbonID());
        }
    }

    public static final int totalSpan = 100;
    private int currentSpan;
    private int spanConstant = 20;

    public GridLayoutManager.SpanSizeLookup getScalableSpanSizeLookUp() {
        return scalableSpanSizeLookUp;
    }

    public int calculateRange() {
        int start = ((GridLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
        int end = ((GridLayoutManager) mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
        if (start < 0)
            start = 0;
        if (end < 0)
            end = 0;
        return end - start;
    }


    private GridLayoutManager.SpanSizeLookup scalableSpanSizeLookUp = new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            return getCurrentSpan();
        }
    };


    public int getCurrentSpan() {
        return currentSpan;
    }

    public void setCurrentSpan(int span) {
        this.currentSpan = span;

    }

    public void delayedNotify(final int pos, final int range) {
        mRecyclerView.postDelayed(new Runnable() {
            @Override
            public void run() {
                notifyItemRangeChanged(pos - range > 0 ? pos - range : 0, range * 2 < getItemCount() ? range * 2 : range);
            }
        }, 100);
    }

    public void updateDataSet(String param) {
        //Fill mItems with your custom list
        //this.mItems = data;
    }

    public void clearData() {
        if (mResults != null) {
            int size = mResults.size();
            if (size > 0) {
                if (mItems != null)
                    mItems.clear();
                if (mResults != null)
                    mResults.clear();
                if (mRibbonIDs != null)
                    mRibbonIDs.clear();
                this.notifyDataSetChanged();
            }
        } else {
            this.notifyDataSetChanged();
        }
    }

    public void addItem(Anime anime) {
        if (anime == null)
            return;
        checkAnime(anime);
        if (mItems != null) {
            this.mItems.add(anime);
        }
        if (mResults != null)
            mResults.add(anime);
        notifyItemInserted(mResults.size() - 1);
    }

    public void addItem(int position, Anime anime) {
        if (anime == null)
            return;
        checkAnime(anime);
        if (mItems != null) {
            this.mItems.add(position, anime);
        }
        if (mResults != null)
            mResults.add(position, anime);
        notifyItemInserted(position);
    }

    public void setAnimeList(ArrayList<Anime> animeList) {
        if (animeList == null || animeList == Collections.EMPTY_LIST)
            return;
        checkAnime(animeList);
        int startPos = 0;
        this.mItems = animeList;
        mResults = (List<Anime>) ((ArrayList<Anime>) mItems).clone();
        this.notifyItemRangeInserted(startPos, animeList.size() - 1);
    }

    public void addAnimeList(List<Anime> animeList) {
        if (animeList == null || animeList == Collections.EMPTY_LIST)
            return;
        checkAnime(animeList);
        int startPos = 0;
        if (mItems != null) {
            startPos = this.mItems.size();
            this.mItems.addAll(animeList);
        } else {
            this.mItems = animeList;
        }
        mResults = (List<Anime>) ((ArrayList<Anime>) mItems).clone();
        this.notifyItemRangeInserted(startPos, animeList.size() - 1);
    }

    public void removeItem(int position) {
        if (position < 0) return;
        if (position < mItems.size()) {
            mItems.remove(position);
            mResults = (List<Anime>) ((ArrayList<Anime>) mItems).clone();
            notifyItemRemoved(position);
        }
    }

    public void checkAnime(Anime anime) {
        if (anime != null) {
            if (mShowBadge) {
                if (mRibbonIDs == null)
                    mRibbonIDs = new HashMap<>();
                FavoriteTag lTag = null;
                FavoriteRecord lRecord = null;
                if (anime.getTagId() == -1) {
                    lRecord = MAVApplication.getInstance().getRepository().getFavoriteByAnimeUrl(anime.getUrl());
                    if (lRecord != null) {
                        anime.setTagId(lRecord.getTagId());
                    }
                }
                if (anime.getTagId() != -1 && mRibbonIDs.get(anime.getTagId()) < 0) {
                    lTag = MAVApplication.getInstance().getRepository().getFavoriteTag(anime.getTagId());
                    if (lTag != null)
                        mRibbonIDs.put(lTag.getId(), lTag.getRibbonID());
                }
            }
        }
    }

    public void checkAnime(List<Anime> animeList) {
        if (animeList != null) {
            if (mShowBadge) {
                if (mRibbonIDs == null)
                    mRibbonIDs = new HashMap<>();
                FavoriteTag lTag = null;
                FavoriteRecord lRecord = null;
                for (Anime anime : animeList) {
                    if (mCatalogType != -1) {
                        //anime.setCatalogPosition(AnimeHelper.getInstance(mContext).getAnimePositionByUrl(mCatalogType, anime.getUrl()));
                    }
                    if (anime.getTagId() == -1) {
                        lRecord = MAVApplication.getInstance().getRepository().getFavoriteByAnimeUrl(anime.getUrl());
                        if (lRecord != null) {
                            anime.setTagId(lRecord.getTagId());
                        }
                    }
                    if (anime.getTagId() != -1 && !mRibbonIDs.containsKey(anime.getTagId())) {
                        lTag = MAVApplication.getInstance().getRepository().getFavoriteTag(anime.getTagId());
                        if (lTag != null)
                            mRibbonIDs.put(lTag.getId(), lTag.getRibbonID());
                    }
                }
            }
        }
    }

    public int getOriginalItemCount() {
        return mItems.size();
    }

    @Override
    public int getItemCount() {
        return mResults.size();
    }

    public Anime getItem(int position) {
        if (position >= mResults.size() || position < 0)
            return null;
        return mResults.get(position);
    }

    public Anime getItem(String url) {
        for (Anime lAnime : mResults) {
            if (lAnime.getUrl().equals(url))
                return lAnime;
        }
        return null;
    }

    public int getItemPosition(String url) {
        for (int i = 0; i < mResults.size(); i++) {
            if (mResults.get(i).getUrl().equals(url))
                return i;
        }
        return -1;
    }

    @Override
    public void setMode(int mode) {
        super.setMode(mode);
        notifyDataSetChanged();
        if (mode == MODE_SINGLE) mLastItemInActionMode = true;
    }

    @Override
    public void selectAll() {
        mSelectAll = true;
        super.selectAll();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //Log.d(TAG, "onCreateViewHolder for viewType " + viewType);
        if (mInflater == null) {
            mInflater = LayoutInflater.from(parent.getContext());
        }
        View v = null;
        ViewHolder viewHolder = null;
        switch (mDisplayType) {
            case 1:
            case 2:
                v = mInflater.inflate(R.layout.anime_list_card, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
            case 3:
                v = mInflater.inflate(R.layout.anime_list_card2, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.alc_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
            default:
                if (mLoaderType < 4)
                    v = mInflater.inflate(R.layout.anime_grid_item, parent, false);
                viewHolder = new ViewHolder(v, this);
                if (mShowBadge) {
                    viewHolder.mTopLeftBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.agi_badge_view_left));
                    viewHolder.mTopLeftBadgeView.setBadgePosition(BadgeView.POSITION_TOP_LEFT);
                    viewHolder.mTopRightBadgeView = new BadgeView(mContext, (View) v.findViewById(R.id.agi_badge_view_right));
                    viewHolder.mTopRightBadgeView.setBadgePosition(BadgeView.POSITION_TOP_RIGHT);
                }
                break;
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
        final Anime anime = mResults.get(position);

        //When user scrolls this bind the correct selection status
        viewHolder.bindAnime(position, anime);
        ViewCompat.setActivated(viewHolder.itemView, isSelected(position));
        //viewHolder.itemView.setActivated(isSelected(position));
        if (!TextUtils.isEmpty(anime.getCover()) && !(mDisplayType == 1)) {
            viewHolder.mImageView.setVisibility(View.VISIBLE);
            ImageLoaderManager.getInstance().loadImage(anime.getCover(), viewHolder.mImageView, new PaletteTransformation.PaletteCallback(viewHolder.mImageView) {

                @Override
                public void onError() {

                }

                @Override
                protected void onSuccess(Palette palette) {
                    if (mDisplayType < 1) {
                        viewHolder.mTitleView.setTextColor(ThemeManager.getInstance().getTextColor());
                        int colorTemp = ThemeManager.getInstance().getBackgroundColor(mContext);
                        if (viewHolder.mTitleView != null && palette != null)
                            if (palette.getVibrantSwatch() != null)
                                colorTemp = palette.getVibrantSwatch().getRgb();
                            else if (palette.getDarkMutedSwatch() != null)
                                colorTemp = palette.getDarkMutedSwatch().getRgb();
                        Integer colorFrom = ThemeManager.getInstance().getBackgroundColor(mContext);
                        Integer colorTo = colorTemp;
                        viewHolder.mPaletteColor = colorTo;
                        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
                        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                            @Override
                            public void onAnimationUpdate(ValueAnimator animator) {
                                if (viewHolder.mCardView == null)
                                    viewHolder.mTitleView.setBackgroundColor((Integer) animator.getAnimatedValue());
                            }

                        });
                        colorAnimation.start();
                    }
                }
            }, viewHolder);
        } else {
            viewHolder.mImageView.setVisibility(View.GONE);
        }

        if (mDisplayType > 0) {
            if (mDisplayType != 3) {
                if (viewHolder.mSubTextView != null) {
                    if (!ThemeManager.getInstance().isLightBackground())
                        viewHolder.mSubTextView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                    String lSubText = "";
                    switch (mSecondaryType) {
                        default:
                        case NONE:
                            break;
                        case GENRE:
                            lSubText = anime.getGenres();
                            break;
                        case CREATOR:
                            lSubText = anime.getCreator();
                            break;
                        case LATEST_EPISODE:
                            lSubText = anime.getLatestEpisode();
                            break;
                        case SOURCE:
                            lSubText = Parser.getNameByUrl(anime.getUrl());
                            break;
                        case SOURCE_AND_LANG:
                            String lLanguage = mContext.getResources().getString(Parser.getExistingInstance(Parser.getTypeByURL(anime.getUrl())).getLanguageResId());
                            lSubText = Parser.getNameByUrl(anime.getUrl()) + " - "
                                    + lLanguage;
                            break;
                    }
                    if (!TextUtils.isEmpty(lSubText)) {
                        viewHolder.mSubTextView.setText(lSubText);
                        viewHolder.mSubTextView.setVisibility(View.VISIBLE);
                    } else
                        viewHolder.mSubTextView.setVisibility(View.INVISIBLE);
                }
                if (viewHolder.mSourceTextView != null) {
                    viewHolder.mSourceTextView.setText(Parser.getNameByUrl(anime.getUrl()));
                }
            } else {
                if (viewHolder.mAuthorView != null) {
                    if (!TextUtils.isEmpty(anime.getCreator())) {
                        viewHolder.mAuthorView.setTypeface(null, Typeface.ITALIC);
                        viewHolder.mAuthorView.setText(anime.getCreator());
                    } else {
                        viewHolder.mAuthorView.setText("");
                    }
                }
                if (viewHolder.mGenreView != null) {
                    if (!TextUtils.isEmpty(anime.getGenres())) {
                        viewHolder.mGenreView.setText(anime.getGenres());
                    } else {
                        viewHolder.mGenreView.setText("");
                    }
                }
                if (viewHolder.mLatestView != null) {
                    //if (!ThemeManager.getInstance().isLightBackground())
                    //  viewHolder.mLatestView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                    if (!TextUtils.isEmpty(anime.getLatestEpisode())) {
                        viewHolder.mLatestView.setText(anime.getLatestEpisode());
                        viewHolder.mLatestView.setSelected(true);
                    } else {
                        viewHolder.mLatestView.setText("");
                    }
                }
                if (viewHolder.mSourceView != null) {
                    //if (!ThemeManager.getInstance().isLightBackground())
                    //    viewHolder.mSourceView.setTextColor(ThemeManager.getInstance().getInvertedTextColor());
                    viewHolder.mSourceView.setText(Parser.getNameByUrl(anime.getUrl()));
                }
            }
        }

        if (mShowBadge) {
            if (anime.getTagId() != -1) {
                if (mRibbonIDs != null && mRibbonIDs.containsKey(anime.getTagId()))
                    viewHolder.mTopRightBadgeView.setBackgroundResource(Constants.RibbonDrawable[mRibbonIDs.get(anime.getTagId())]);
                else {
                    if (anime.getTagId() < Constants.RibbonDrawable.length)
                        viewHolder.mTopRightBadgeView.setBackgroundResource(Constants.RibbonDrawable[anime.getTagId()]);
                }
                viewHolder.mTopRightBadgeView.show();
            } else {
                viewHolder.mTopRightBadgeView.setBackgroundResource(0);
                viewHolder.mTopRightBadgeView.hide();
            }
            if (viewHolder.mTopLeftBadgeView != null) {
                if (anime.hasNewEpisodes()) {
                    viewHolder.mTopLeftBadgeView.setText("New");
                    viewHolder.mTopLeftBadgeView.show();
                } else {
                    viewHolder.mTopLeftBadgeView.setBackgroundResource(0);
                    viewHolder.mTopLeftBadgeView.hide();
                }
            }
        }

        if (mSelectAll || mLastItemInActionMode) {
            //Reset the flags with delay
            if (viewHolder.mCheckView != null)
                viewHolder.mCheckView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mSelectAll = mLastItemInActionMode = false;
                    }
                }, 200L);
        }

        if (viewHolder.mCheckView != null) {
            if (getMode() == MODE_MULTI) {
                if (isSelected(position)) {
                    viewHolder.mCheckView.setChecked(true);
                } else {
                    viewHolder.mCheckView.setChecked(false);
                }
            } else {
                viewHolder.mCheckView.setChecked(false);
            }
        }
    }

    @Override
    public void onViewRecycled(ViewHolder holder) {
        super.onViewRecycled(holder);
        Glide.clear(holder.mImageView);
        Picasso.with(mContext).cancelRequest(holder.mImageView);
    }

    static int mReadColor = Color.GRAY;
    static int mUnreadColor = Color.WHITE;
    static int mLastReadColor = Color.RED;

    /**
     * Provide a reference to the views for each data item.
     * Complex data labels may need more than one view per item, and
     * you provide access to all the views for a data item in a view holder.
     */
    public static final class ViewHolder extends RecyclerView.ViewHolder implements
            View.OnTouchListener,
            View.OnClickListener,
            View.OnLongClickListener {

        AnimeRecyclerAdapter mAdapter;

        public AppCompatTextView mTitleView;
        AppCompatImageView mImageView;
        AppCompatTextView mSubTextView = null;
        AppCompatTextView mSourceTextView = null;

        AppCompatTextView mAuthorView = null;
        AppCompatTextView mGenreView = null;
        AppCompatTextView mLatestView = null;
        AppCompatTextView mSourceView = null;
        BadgeView mTopLeftBadgeView = null;
        BadgeView mTopRightBadgeView = null;
        AppCompatCheckBox mCheckView = null;
        CardView mCardView = null;

        Anime mAnime;

        public int mPosition = -1, mLastTouchX, mLastTouchY, mPaletteColor = -1;

        ViewHolder(View itemView, final AnimeRecyclerAdapter adapter) {
            super(itemView);

            this.mAdapter = adapter;
            switch (mAdapter.mDisplayType) {
                case 1:
                case 2:
                case 3:
                    mCardView = itemView.findViewById(R.id.alc_cardLayout);
                    mTitleView = itemView.findViewById(R.id.alc_text);
                    mSubTextView = itemView.findViewById(R.id.alc_text2);
                    mSourceTextView = itemView.findViewById(R.id.alc_text3);
                    mImageView = (SquareImageView) itemView.findViewById(R.id.alc_image);
                    mCheckView = itemView.findViewById(R.id.alc_checkbox);

                    mAuthorView = mSubTextView;
                    mGenreView = mSourceTextView;
                    mSourceView = itemView.findViewById(R.id.alc_text4);
                    mLatestView = itemView.findViewById(R.id.alc_text5);
                    break;
                default:
                    mCheckView = itemView.findViewById(R.id.agi_checkBox);
                    mTitleView = itemView.findViewById(R.id.agi_text);
                    mImageView = itemView.findViewById(R.id.agi_image);
                    break;
            }
            this.itemView.setOnTouchListener(this);
            this.itemView.setOnClickListener(this);
            this.itemView.setOnLongClickListener(this);

            /**
             if (mCardView != null) {
             if (ThemeManager.getInstance().isLightBackground())
             mCardView.setCardBackgroundColor(ThemeManager.getInstance(mAdapter.mContext).getTextColor());
             else
             mCardView.setCardBackgroundColor(Color.parseColor("#f9f9f9"));
             }
             **/
        }

        public void bindAnime(int position, Anime anime) {
            mLastTouchX = 0;
            mLastTouchY = 0;
            mPosition = position;
            mAnime = anime;
            if (mCheckView != null) {
                if (mAdapter.getMode() == MODE_MULTI)
                    mCheckView.setVisibility(View.VISIBLE);
                else {
                    mCheckView.setChecked(false);
                    mCheckView.setVisibility(View.GONE);
                }
            }
            mTitleView.setText(mAnime.getTitle());
        }

        /**
         * Perform animation and selection on the current ItemView.
         * <br/><br/>
         * <b>IMPORTANT NOTE!</b> <i>setActivated</i> changes the selection color of the item
         * background if you added<i>android:background="?attr/selectableItemBackground"</i>
         * on the row layout AND in the style.xml.
         * <br/><br/>
         * This must be called after the listener consumed the event in order to add the
         * item number in the selection list.<br/>
         * Adapter must have a reference to its instance to check selection state.
         * <br/><br/>
         * If you do this, it's not necessary to invalidate the row (with notifyItemChanged): In this way
         * <i>onBindViewHolder</i> is NOT called on selection and custom animations on objects are NOT interrupted,
         * so you can SEE the animation in the Item and have the selection smooth with ripple.
         */
        private void toggleActivation() {
            //itemView.setActivated(mAdapter.isSelected(getAdapterPosition()));
            ViewCompat.setActivated(itemView, mAdapter.isSelected(getAdapterPosition()));
            if (itemView.isActivated()) {
                if (mCheckView != null)
                    mCheckView.setChecked(true);
            } else {
                if (mCheckView != null)
                    mCheckView.setChecked(false);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onClick(View view) {
            if (mAdapter.mClickListener.onListItemClick(getAdapterPosition()))
                toggleActivation();
            else {
                mAnime.setNewEpisodes(0);
                AnimeUtils.viewAnime(mAdapter.mContext, mImageView, mAnime);
                if (mPosition != -1)
                    mAdapter.notifyItemChanged(mPosition);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean onLongClick(View view) {
            mAdapter.mClickListener.onListItemLongClick(getAdapterPosition());
            toggleActivation();
            return true;
        }

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mLastTouchX = (int) event.getRawX();
                mLastTouchY = (int) event.getRawY();
            }
            return false;
        }
    }

    private class AnimeFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            // We implement here the filter logic
            if (constraint == null || constraint.length() == 0) {
                // No filter implemented we return all the list
                results.values = mItems;
                results.count = mItems.size();
            } else {
                // We perform filtering operation
                List<Anime> nAnimeList = new ArrayList<Anime>();

                for (Anime p : mItems) {
                    if (p.getTitle().toUpperCase().startsWith(constraint.toString().toUpperCase()))
                        nAnimeList.add(p);
                }

                results.values = nAnimeList;
                results.count = nAnimeList.size();

            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {

            // Now we have to inform the adapter about the new list filtered
            if (results.count == 0)
                notifyDataSetChanged();
            else {
                mResults = (List<Anime>) results.values;
                notifyDataSetChanged();
            }

        }
    }

    private class AnimeCustomFilter extends Filter {
        boolean mSourceCheck = true;
        boolean mPriorityCheck = true;

        public AnimeCustomFilter() {
        }

        public AnimeCustomFilter(boolean priorityCheck, boolean sourceCheck) {
            mPriorityCheck = priorityCheck;
            mSourceCheck = sourceCheck;
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            // NOTE: this function is *always* called from a background thread, and
            // not the UI thread.
            constraint = constraint.toString().toLowerCase();
            FilterResults result = new FilterResults();
            if (constraint != null && constraint.toString().length() > 0) {
                mSourceCheck = !constraint.toString().contains("all");
                ArrayList<Anime> filt = new ArrayList<Anime>();
                ArrayList<Anime> lItems = new ArrayList<Anime>();
                synchronized (this) {
                    lItems.addAll(mItems);
                }
                for (int i = 0, l = lItems.size(); i < l; i++) {
                    Anime m = lItems.get(i);
                    String lPriority = String.valueOf(m.getTagId());
                    String lSource = Parser.getNameByUrl(m.getUrl()).toLowerCase();
                    boolean lPriorityCheck = !mPriorityCheck || constraint.toString().contains(lPriority);
                    boolean lSourceCheck = !mSourceCheck || constraint.toString().contains(lSource);
                    if (lPriorityCheck && lSourceCheck)
                        filt.add(m);
                }
                result.count = filt.size();
                result.values = filt;
            } else {
                synchronized (this) {
                    result.values = mItems;
                    result.count = mItems.size();
                }
            }
            return result;
        }

        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {

            // Now we have to inform the adapter about the new list filtered
            if (results.count == 0) {
                mResults.clear();
                notifyDataSetChanged();
            } else {
                mResults = (List<Anime>) results.values;
                notifyDataSetChanged();
            }
        }
    }
} public class FavoriteListAdapter extends ArrayAdapter<FavoriteTag> {
    Context mContext;
    boolean mShowDeleteOption = false;

    public FavoriteListAdapter(Context pContext) {
        super(pContext, R.layout.favorite_row, MAVApplication.getInstance().getRepository().getFavoriteTags());
        mContext = pContext;
    }

    public FavoriteListAdapter(Context pContext, boolean canDelete) {
        super(pContext, R.layout.favorite_row, MAVApplication.getInstance().getRepository().getFavoriteTags());
        mContext = pContext;
        mShowDeleteOption = canDelete;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void resetData() {
        clear();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            addAll(MAVApplication.getInstance().getRepository().getFavoriteTags());
        } else {
            for (FavoriteTag obj : MAVApplication.getInstance().getRepository().getFavoriteTags()) {
                add(obj);
            }
        }
        add(new FavoriteTag(-1, "Add New", -1));
        notifyDataSetChanged();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        FavoriteHolder holder = null;
        if (row == null) {
            LayoutInflater inflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflater.inflate(R.layout.favorite_row, null);

            holder = new FavoriteHolder(row);
            row.setTag(holder);
        } else {
            holder = (FavoriteHolder) row.getTag();
        }

        final FavoriteTag lTag = getItem(position);
        if (lTag != null) {
            String subText = "";
            if (lTag.getId() != -1)
                subText = " - " + lTag.getId();
            holder.titleView.setText(lTag.getTitle() + subText);
            if (getItem(position).getRibbonID() > -1 && lTag.getRibbonID() < Constants.RibbonDrawable.length) {
                holder.coverImageView.setImageResource(Constants.RibbonDrawable[getItem(position).getRibbonID()]);
            } else {
                holder.coverImageView.setImageResource(0);
            }

            if (mShowDeleteOption && lTag.getId() != -1) {
                holder.deleteButton.setVisibility(View.VISIBLE);
                holder.deleteButton.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        new AlertDialog.Builder(mContext)
                                .setTitle(R.string.remove_tag)
                                .setMessage(R.string.confirmation_msg)
                                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.cancel();
                                        if (MAVApplication.getInstance().getRepository().deleteFavoriteTag(lTag.getTagId())) {
                                            MAVApplication.getInstance().getRepository().deleteFavoriteRecordsByTagID(lTag.getTagId());
                                            remove(lTag);
                                            notifyDataSetChanged();
                                        }
                                    }
                                })
                                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.cancel();
                                    }
                                }).show();
                    }
                });
            }
        }

        return (row);
    }

    static class FavoriteHolder {
        private TextView titleView = null;
        private ImageView coverImageView = null;
        private ImageButton deleteButton = null;

        FavoriteHolder(View row) {
            titleView = (TextView) row.findViewById(R.id.fr_text);
            coverImageView = (ImageView) row
                    .findViewById(R.id.fr_image);
            deleteButton = (ImageButton) row.findViewById(R.id.fr_delete);
        }
    }
} public interface OnItemClickListener {
        /**
         *
         * @param position
         * @return true if MULTI selection is enabled, false for SINGLE selection
         */
        boolean onListItemClick(int position);

        /**
         *
         * @param position
         */
        void onListItemLongClick(int position);
    } public class AnimeListLoader extends AsyncTaskLoader<ArrayList<Anime>> {

    public static final String mTag = AnimeListLoader.class.getSimpleName();

    /**
     * Helper for determining if the configuration has changed in an interesting
     * way so we need to rebuild the app list.
     */
    public static class InterestingConfigChanges {
        final Configuration mLastConfiguration = new Configuration();
        int mLastDensity;

        boolean applyNewConfig(Resources res) {
            int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
            boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
            if (densityChanged || (configChanges & (ActivityInfo.CONFIG_LOCALE
                    | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
                mLastDensity = res.getDisplayMetrics().densityDpi;
                return true;
            }
            return false;
        }
    }

    /**
     * Helper class to look for interesting changes to the installed apps
     * so that the loader can be updated.
     */
    public static class AnimeIntentReceiver extends BroadcastReceiver {
        final AnimeListLoader mLoader;

        public AnimeIntentReceiver(AnimeListLoader loader) {
            mLoader = loader;
            IntentFilter filter = new IntentFilter(Constants.Intents.ANIME_UPDATED);
            mLoader.getContext().registerReceiver(this, filter);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            // Tell the loader about the change.
            mLoader.onContentChanged();
        }
    }

    final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();

    private ArrayList<Anime> mApps;
    private AnimeIntentReceiver mPackageObserver;
    private Parser mParser;
    private String mURL;
    private boolean mReturnAll;

    public AnimeListLoader(Context context, Parser parser, String url, boolean returnAll) {
        super(context);
        mParser = parser;
        mURL = url;
        mReturnAll = returnAll;
    }

    /**
     * This is where the bulk of our work is done.  This function is
     * called in a background thread and should generate a new set of
     * data to be published by the loader.
     */
    @Override
    public ArrayList<Anime> loadInBackground() {
        WriteLog.appendLog(mTag, "loadInBackground called");
        final Context context = getContext();

        // Create corresponding array of entries and load their labels.
        List<Anime> entries = NetworkUtils.isNetworkAvailable(context) ? mParser.getAnimeList(mURL) : null;
        List<Anime> lNonCachedTitles = new ArrayList<Anime>();
        List<Anime> lCachedTitles = new ArrayList<Anime>();

        if (entries != null && !entries.isEmpty()) {
            HashMap<String, Anime> animeMap = MAVApplication.getInstance().getRepository().getAnimeMapByUrl(mParser.getServerUrl());
            for (Anime entry : entries) {
                if (animeMap.containsKey(entry.getUrl())) {
                    Anime cachedAnime = animeMap.get(entry.getUrl());
                    AnimeHelper.update(entry, cachedAnime);
                    lCachedTitles.add(cachedAnime);
                } else {
                    lNonCachedTitles.add(entry);
                }
            }
            if (!lNonCachedTitles.isEmpty())
                MAVApplication.getInstance().getRepository().insertAnimeList(lNonCachedTitles);
            if (!lCachedTitles.isEmpty()) {
                MAVApplication.getInstance().getRepository().updateAnimeList(lCachedTitles);
            }
        }

        if (entries != null)
            WriteLog.appendLog(mTag, entries.size() + " anime loaded from " + mURL);
        else
            WriteLog.appendLog(mTag, "No internet connection");
        WriteLog.appendLog(mTag, lNonCachedTitles.size() + " new anime loaded from " + mURL);
        WriteLog.appendLog(mTag, lCachedTitles.size() + " cached anime loaded from " + mURL);

        // Done!
        if (mReturnAll)
            return (ArrayList<Anime>) entries;
        else
            return (ArrayList<Anime>) lNonCachedTitles;
    }

    /**
     * Called when there is new data to deliver to the client.  The
     * super class will take care of delivering it; the implementation
     * here just adds a little more logic.
     */
    @Override
    public void deliverResult(ArrayList<Anime> apps) {
        if (isReset()) {
            // An async query came in while the loader is stopped.  We
            // don't need the result.
            if (apps != null) {
                onReleaseResources(apps);
            }
        }
        ArrayList<Anime> oldApps = apps;
        mApps = apps;

        if (isStarted()) {
            // If the Loader is currently started, we can immediately
            // deliver its results.
            super.deliverResult(apps);
        }

        // At this point we can release the resources associated with
        // 'oldApps' if needed; now that the new result is delivered we
        // know that it is no longer in use.
        if (oldApps != null) {
            onReleaseResources(oldApps);
        }
    }

    /**
     * Handles a request to start the Loader.
     */
    @Override
    protected void onStartLoading() {
        if (mApps != null) {
            // If we currently have a result available, deliver it
            // immediately.
            deliverResult(mApps);
        }

        // Start watching for changes in the app data.
        if (mPackageObserver == null) {
            mPackageObserver = new AnimeIntentReceiver(this);
        }

        // Has something interesting in the configuration changed since we
        // last built the app list?
        boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());

        if (takeContentChanged() || mApps == null || configChange) {
            // If the data has changed since the last time it was loaded
            // or is not currently available, start a load.
            forceLoad();
        }
    }

    /**
     * Handles a request to stop the Loader.
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    /**
     * Handles a request to cancel a load.
     */
    @Override
    public void onCanceled(ArrayList<Anime> apps) {
        super.onCanceled(apps);

        // At this point we can release the resources associated with 'apps'
        // if needed.
        onReleaseResources(apps);
    }

    /**
     * Handles a request to completely reset the Loader.
     */
    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        // At this point we can release the resources associated with 'apps'
        // if needed.
        if (mApps != null) {
            onReleaseResources(mApps);
            mApps = null;
        }

        // Stop monitoring for changes.
        if (mPackageObserver != null) {
            getContext().unregisterReceiver(mPackageObserver);
            mPackageObserver = null;
        }
    }

    /**
     * Helper function to take care of releasing resources associated
     * with an actively loaded data set.
     */
    protected void onReleaseResources(ArrayList<Anime> apps) {
        // For a simple List<> there is nothing to do.  For something
        // like a Cursor, we would close it here.
    }
} @Entity
public class Anime {

    @Id
    private long id = 0;
    private String title;
    private String url;
    private String cover;

    private String type;
    private String aliases;
    private String creator;
    private String genres;
    private String summary;
    @Transient
    private String warning;
    private Date date;
    private int status = -1;


    @Backlink(to = "animeId")
    private List<Episode> episodes = new ArrayList<>();

    private String latestEpisode;
    private String latestUpdateTime;
    private int tagId = -1;

    @Transient
    private int newEpisodes = -1;
    @Transient
    private int unviewedEpisodes = -1;
    @Transient
    private int episodeCount = -1;
    @Transient
    private int catalogPosition;

    /** Used to resolve relations */
    @Internal
    @Generated(1307364262)
    transient BoxStore __boxStore;

    @Generated(2088667730)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public Anime(long id, String title, String url, String cover, String type,
            String aliases, String creator, String genres, String summary,
            Date date, int status, String latestEpisode, String latestUpdateTime,
            int tagId) {
        this.id = id;
        this.title = title;
        this.url = url;
        this.cover = cover;
        this.type = type;
        this.aliases = aliases;
        this.creator = creator;
        this.genres = genres;
        this.summary = summary;
        this.date = date;
        this.status = status;
        this.latestEpisode = latestEpisode;
        this.latestUpdateTime = latestUpdateTime;
        this.tagId = tagId;
    }

    @Generated(1101169224)
    public Anime() {
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    @Override
    public String toString() {
        return "Anime [name=" + title + ", "
                + "url=" + url + ", "
                + "cover=" + cover + ", "
                + "aliases=" + aliases + ", "
                + "creator=" + creator + ", "
                + "summary=" + summary + ", "
                + "genres=" + genres + ", "
                + "warning=" + warning + ", "
                + "episodes=" + episodes + "]";
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getType() {
        return type;
    }

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

    public String getAliases() {
        return aliases;
    }

    public void setAliases(String aliases) {
        this.aliases = aliases;
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        this.creator = creator;
    }

    public String getGenres() {
        return genres;
    }

    public void setGenres(String genres) {
        this.genres = genres;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getWarning() {
        return warning;
    }

    public void setWarning(String warning) {
        this.warning = warning;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public List<Episode> getEpisodes() {
        return episodes;
    }

    public void setEpisodes(List<Episode> episodes) {
        this.episodes = episodes;
    }

    public String getLatestEpisode() {
        return latestEpisode;
    }

    public void setLatestEpisode(String latestEpisode) {
        this.latestEpisode = latestEpisode;
    }

    public String getLatestUpdateTime() {
        return latestUpdateTime;
    }

    public void setLatestUpdateTime(String latestUpdateTime) {
        this.latestUpdateTime = latestUpdateTime;
    }

    public int getTagId() {
        return tagId;
    }

    public void setTagId(int tagId) {
        this.tagId = tagId;
    }

    public int getNewEpisodes() {
        return newEpisodes;
    }

    public void setNewEpisodes(int newEpisodes) {
        this.newEpisodes = newEpisodes;
    }

    public int getUnviewedEpisodes() {
        return unviewedEpisodes;
    }

    public void setUnviewedEpisodes(int unviewedEpisodes) {
        this.unviewedEpisodes = unviewedEpisodes;
    }

    public int getEpisodeCount() {
        return episodeCount;
    }

    public void setEpisodeCount(int episodeCount) {
        this.episodeCount = episodeCount;
    }

    public boolean hasNewEpisodes() {
        return newEpisodes > 0;
    }

    public int getCatalogPosition() {
        return catalogPosition;
    }

    public void setCatalogPosition(int catalogPosition) {
        this.catalogPosition = catalogPosition;
    }

    public enum Order implements Comparator<Anime> {
        ByNameAZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {

                String lTitle1 = anime1.getTitle().toLowerCase();
                String lTitle2 = anime2.getTitle().toLowerCase();

                // ascending order
                return lTitle1.compareTo(lTitle2);
            }
        },
        ByNameZA() {
            @Override
            public int compare(Anime anime1, Anime anime2) {

                String lTitle1 = anime1.getTitle().toLowerCase();
                String lTitle2 = anime2.getTitle().toLowerCase();

                // ascending order
                return lTitle2.compareTo(lTitle1);
            }
        },
        ByTagIdL2H() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int lPriority1 = anime1.getTagId();
                int lPriority2 = anime2.getTagId();

                if (lPriority1 == lPriority2)
                    return 0;
                else if (lPriority1 > lPriority2)
                    return 1;
                else
                    return -1;
            }
        },
        ByTagIdL2H_AZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int lPriority1 = anime1.getTagId();
                int lPriority2 = anime2.getTagId();

                if (lPriority1 > lPriority2)
                    return 1;
                else if (lPriority1 < lPriority2)
                    return -1;
                else {
                    return ByNameAZ.compare(anime1, anime2);
                }
            }
        },
        ByTagIdH2L_AZ() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                int tagId1 = anime1.getTagId();
                int tagId2 = anime2.getTagId();

                if (tagId1 < tagId2)
                    return 1;
                else if (tagId1 > tagId2)
                    return -1;
                else {
                    return ByNameAZ.compare(anime1, anime2);
                }
            }
        },
        ByLastUpdate() {
            @Override
            public int compare(Anime anime1, Anime anime2) {
                //long lUpdateTime1 = anime1.getLastUpdate();
                //long lUpdateTime2 = anime2.getLastUpdate();
                long lUpdateTime1 = 0;
                long lUpdateTime2 = 0;
                if (lUpdateTime1 == lUpdateTime2)
                    return 0;
                else if (lUpdateTime1 > lUpdateTime2)
                    return -1;
                else
                    return 1;
            }
        },
    }
} @Entity
public class FavoriteTag {

    @Id
    private long id = 0;
    private int tagId = -1;
    private String title = "";
    private int ribbonId = -1;

    @Generated(30053616)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public FavoriteTag(long id, int tagId, String title, int ribbonId) {
        this.id = id;
        this.tagId = tagId;
        this.title = title;
        this.ribbonId = ribbonId;
    }

    @Generated(786092308)
    public FavoriteTag() {
    }

    public FavoriteTag(int tagId, String title, int ribbonId) {
        this.tagId = tagId;
        this.title = title;
        this.ribbonId = ribbonId;
    }

    @Override
    public String toString() {
        return "FavoriteTag [tagId=" + tagId + ", title=" + title + ", ribbonId=" + ribbonId + "]";
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getRibbonID() {
        return ribbonId;
    }

    public void setRibbonID(int ribbonID) {
        ribbonId = ribbonID;
    }

    public int getRibbonId() {
        return ribbonId;
    }

    public void setRibbonId(int ribbonId) {
        this.ribbonId = ribbonId;
    }

    public int getTagId() {
        return tagId;
    }

    public void setTagId(int tagId) {
        this.tagId = tagId;
    }

    public enum Order implements Comparator<FavoriteTag> {
        ByTagIdLowToHigh() {
            @Override
            public int compare(FavoriteTag tag1, FavoriteTag tag2) {
                int tagId1 = tag1.getTagId();
                int tagId2 = tag2.getTagId();

                if (tagId1 == tagId2)
                    return 0;
                else if (tagId1 > tagId2)
                    return 1;
                else
                    return -1;
            }
        }
    }
} public class AnimeHelper {

    public static void update(Anime animeSource, Anime animeDestination) {
        if (!TextUtils.isEmpty(animeSource.getUrl()))
            animeDestination.setUrl(animeSource.getUrl());
        if (animeSource.getStatus() != -1)
            animeDestination.setStatus(animeSource.getStatus());
        if (!TextUtils.isEmpty(animeSource.getTitle()))
            animeDestination.setTitle(animeSource.getTitle());
        if (!TextUtils.isEmpty(animeSource.getGenres()))
            animeDestination.setGenres(animeSource.getGenres());
        if (animeSource.getEpisodes() != null && !animeSource.getEpisodes().isEmpty()) {
            for (Episode episode : animeSource.getEpisodes()) {
                Episode cachedEpisode = MAVApplication.getInstance().getRepository().getEpisodeByUrl(episode.getUrl());
                if (cachedEpisode != null)
                    episode.setId(cachedEpisode.getId());
                episode.setAnime(animeDestination);
                animeDestination.getEpisodes().add(episode);
            }
        }
    }
} public abstract class Parser {

    private static Parser instance = null;

    public String name;
    public boolean hasCatalog = true;
    public boolean isCatalogFilterable = true;
    public boolean isCatalogSortSupported = false;
    public boolean isLatestSortSupported = false;
    public boolean isShowAllSupported = false;
    public boolean isGridViewSupported = false;
    public boolean isGenreSortSupported = false;
    public boolean isCloudFlareDDOSEnabled = false;
    public boolean isCloudFlareDDOSPassed = false;
    public String mCookies = null;
    private static final int mHardCount = 1;

    public static String openloadLogin = "92c9a7c7a6fce945";
    public static String openloadKey = "nbqBkkQI";

    @Expose
    public String mCustomUserAgent = "";

    public int mAnimeCount = -1;
    private static int parserType = 0;

    public boolean isPremium = false;

    public static int mParseTimeOut = 120000;
    public static int mConnectTimeOut = 60000;

    protected static ArrayList<Parser> mParserArray = new ArrayList<Parser>();

    static {
        if (mParserArray.size() == 0) {
            Parser lParser = null;
            for (int i = 0; i < mHardCount; i++) {
                lParser = getNewInstance(i);
                if (lParser != null) {
                    lParser.mAnimeCount = getCountByURL(lParser.getServerUrl());
                    mParserArray.add(lParser);
                }
            }
        }
        Collections.sort(mParserArray, Order.ByNameAZ);
    }

    public static int getParserCount() {
        if (mParserArray != null)
            return mParserArray.size();
        else
            return -1;
    }

    OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(mParseTimeOut, TimeUnit.MILLISECONDS)
            .retryOnConnectionFailure(true).build();

    private static Parser getNewInstance(int pType) {
        switch (pType) {
            default:
                return new GoGoAnime();
        }
    }

    /**
     * @param pURL The type by url of a parser, if the type is different from the current parser, it replaces the old one.
     * @return A singleton instance of the parser
     */
    public static Parser getInstance(String pURL) {
        int lType = getTypeByURL(pURL);
        if (instance == null) {
            instance = getExistingInstance(lType);
        }
        return instance;
    }

    /**
     * @param pType The type of parser, if the type is different from the current parser, it replaces the old one.
     * @return A singleton instance of the parser
     */
    public static Parser getInstance(int pType) {
        if (instance == null || parserType != pType) {
            parserType = pType;
            instance = getParser();
        }
        WriteLog.appendLog("Parser Instance " + pType + " " + instance);
        return instance;
    }

    /**
     * @return The current parser, as set by {@link #getInstance(int)}
     */
    private static Parser getParser() {
        if (parserType > -1 && parserType < mParserArray.size())
            return getExistingInstance(parserType);
        else {
            WriteLog.appendLog("getParser() invalid parser id " + parserType);
            return getExistingInstance(0);
        }
    }

    public static ArrayList<Parser> getParserArray() {
        return mParserArray;
    }

    public static int getParseTimeOut() {
        return mParseTimeOut;
    }

    public static void setParseTimeOut(int mParseTimeOut) {
        Parser.mParseTimeOut = mParseTimeOut;
    }

    public String getUserAgent() {
        return TextUtils.isEmpty(mCustomUserAgent) ? mUserAgent : mCustomUserAgent;
    }

    public static String mUserAgent = "Mozilla/5.0 (Linux; Android 4.2.2; en-us; SAMSUNG GT-I9195 Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Version/1.0 Chrome/18.0.1025.308 Mobile Safari/535.19";

    public abstract String getServerUrl();

    public abstract ArrayList<Anime> getAnimeList(String url);

    public abstract Anime getAnimeDetails(String url);

    public abstract String getEpisodeVideo(String url);

    public abstract String[] getDirectoryLinks();

    public abstract String[] getDirectoryTitles();

    public abstract String getDirectoryURL(int pType);

    public abstract String getDirectoryURL(int pType, int pPage);

    public abstract String getDirectoryURL(String pUrl, int pPage);

    public abstract int getDirectoryLowerBound();

    public abstract int getDirectoryUpperBound();

    public boolean getIsPremium() {
        return isPremium;
    }

    public int getLanguageResId() {
        return R.string.english;
    }

    @Deprecated
    public ArrayList<Episode> getEpisodeList() {
        return null;
    }

    public Date getDateFormat(String date) {
        return null;
    }

    public String getRandomAnimeURL() {
        return null;
    }

    public ArrayList<Anime> getFullAnimeList(String pURL) {
        return null;
    }

    public String getFullAnimeListURL() {
        return null;
    }

    public String getLatestAnimeListURL() {
        return null;
    }

    public ArrayList<Anime> getLatestAnimeList(String pURL) {
        return null;
    }

    public static String getNameByUrl(String pURL) {
        if (pURL.contains("animefreak")) {
            return "Anime Freak";
        } else if (pURL.contains("watchanime.me")) {
            return "Watch Anime";
        } else if (pURL.contains("animeshow")) {
            return "Anime Show";
        } else if (pURL.contains("animetv.to")) {
            return "Anime Tv";
        } else if (pURL.contains("gogoanime.io")) {
            return "GoGo Anime";
        }
        return "Unsupported " + pURL;
    }

    public static int getTypeByURL(String pURL) {
        for (int i = 0; i < mParserArray.size(); i++) {
            if (pURL.contains(mParserArray.get(i).getServerUrl()
                    .replace("https://", "")
                    .replace("http://", "")
                    .replace("www.", ""))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @param pURL       The url associated with a manga to find proper parser type for.
     * @param pLangResID
     * @return The type of parser which supports it.
     */
    public static int getTypeByURL(String pURL, int pLangResID) {
        for (int i = 0; i < mParserArray.size(); i++) {
            if (pURL.contains(mParserArray.get(i).getServerUrl().replace("http://", "").replace("www.", ""))
                    && mParserArray.get(i).getLanguageResId() == pLangResID) {
                return i;
            }
        }

        return -1;
    }

    public static Parser getExistingInstance(String typeByUrl) {
        return mParserArray.get(getTypeByURL(typeByUrl));
    }

    public static Parser getExistingInstance(int parserId) {
        return mParserArray.get(parserId);
    }

    public static int getCountByURL(String pURL) {
        if (pURL.contains("anime-joy.tv") || pURL.contains("animejoy.tv")) {
            return 2478;
        } else if (pURL.contains("animefreak.tv")) {
            return 1856;
        } else if (pURL.contains("watchanimesonline.net")) {
            return 3653;
        }
        return -1;
    }

    public String getName() {
        return name;
    }

    public static enum Order implements Comparator<Parser> {
        ByNameAZ() {
            @Override
            public int compare(Parser pParser1, Parser pParser2) {

                String lTitle1 = pParser1.name.toLowerCase();
                String lTitle2 = pParser2.name.toLowerCase();

                // ascending order
                return lTitle1.compareTo(lTitle2);
            }
        },
        BySizeH2L() {
            @Override
            public int compare(Parser pParser1, Parser pParser2) {
                int lPriority1 = getCountByURL(pParser1.getServerUrl());
                int lPriority2 = getCountByURL(pParser2.getServerUrl());

                if (lPriority1 == lPriority2)
                    return 0;
                else if (lPriority1 > lPriority2)
                    return 1;
                else
                    return -1;
            }
        }
    }

    public static boolean isDeadSource(Parser existingInstance) {
        // TODO Auto-generated method stub
        return false;
    }

    protected String getVideoFromProviderByName(String name, String data) {
        if (name.equals("Estream")) {
            return getEstreamToVideo("https://estream.to/"+data);
        } else if (name.equals("Yourupload")) {
            return getYourUploadVideo("https://yourupload.com/embed/"+data);
        } else if (name.equals("Mp4Upload"))
            return getMP4UploadVideo("https://mp4upload.com/"+data);
        //yourupload.com/play/1332473.mp4?busted=1501129123931
        return "";
    }

    public String getVideoFromProvider(String url) {
        WriteLog.appendLog(name, "Video Provider: " + url);
        if (url.contains("auengine")) {
            return getAuEngineVideo(url);
        } else if (url.contains("mp4upload.com")) {
            return getMP4UploadComVideo(url);
        } else if (url.contains("mp4upload")) {
            return getMP4UploadVideo(url);
        } else if (url.contains("safeupload")) {
            return getSafeUploadVideo(url);
        } else if (url.contains("openload.co")) {
            //return getOpenloadCoVideo(url);
        } else if (url.contains("estream.to")) {
            return getEstreamToVideo(url);
        } else if (url.contains("streamango.com")) {
            return getStreamangoVideo(url);
        } else if (url.contains("vidstreaming.io")) {
            return getVidStreamingVideo(url);
        }
        WriteLog.appendLog("Unsupported Video Provider: " + url);
        return url;
    }

    private String getVidStreamingVideo(String url) {
        String videoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(getUserAgent())
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("div[class=videocontent]").select("video[id=my-video-player]").select("source");
            String tempUrl = "";
            if (eles != null && !eles.isEmpty()) {
                for (Element ele : eles) {
                    tempUrl = ele.attr("abs:src");
                    if (!TextUtils.isEmpty(tempUrl))
                        videoUrl = tempUrl;
                }
            } else {
                eles = docdata.select("div[class=videocontent]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    videoUrl = eles.attr("src");
                    return getVideoFromProvider(videoUrl);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return videoUrl;
    }

    public static String getOpenloadCoVideo(String url) {
        String fileId = "";
        String lVideoUrl = "";
        if (url.startsWith("https://openload.co/f/")) {
            String[] splitUrl = url.replace("https://openload.co/f/", "").split("/");
            fileId = splitUrl[0];
        }
        if (url.startsWith("https://openload.co/embed/")) {
            String[] splitUrl = url.replace("https://openload.co/embed/", "").split("/");
            fileId = splitUrl[0];
        }
        /*
        DownloadTicket ticket = OpenloadManager.getInstance().getDownloadTicket(fileId);
        if (ticket.getStatus() == 200) {
            DownloadLink link = OpenloadManager.getInstance().getDownloadLink(fileId, ticket);
            if (link != null) {
                if (link.getStatus() == 200) {
                    lVideoUrl = link.getResult().getUrl();
                }
            }
        }
        */
        return lVideoUrl;
    }

    public String getMP4UploadComVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("div[id=player_code]").select("script");
                lVideoUrl = eles.get(0).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("\"file\": \"(.+?)\"");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getAuEngineVideo(String url) {
        String lVideoUrl = "";
        Document docdata;
        try {
            docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements ele = docdata.select("script");
            Pattern p = Pattern.compile("http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

            String innerHtml = "";
            String videoUrl = "";
            for (int i = 0; i < ele.size(); i++) {
                innerHtml = ele.get(i).html();
                if (!innerHtml.contains("file"))
                    continue;
                Matcher m = p.matcher(innerHtml);
                while (m.find()) {
                    videoUrl = m.group();
                    if (videoUrl.contains(".mp4")) {
                        WriteLog.appendLog("" + " matcher found url " + m.group());
                        lVideoUrl = videoUrl;
                    } else {
                        continue;
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getMP4UploadVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("script + script");
            if (eles != null && !eles.isEmpty()) {
                lVideoUrl = eles.get(eles.size() - 2).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("'file': '(.+?)'");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getSafeUploadVideo(String url) {
        //http://www.safeupload.org/getembed/f93fb4096e0875979215c0307dd53ff5
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = docdata.select("body").select("div + script");
            if (eles != null && !eles.isEmpty()) {
                lVideoUrl = eles.get(eles.size() - 2).html();
                if (!TextUtils.isEmpty(lVideoUrl)) {
                    final Pattern pattern = Pattern.compile("'file': '(.+?)'");
                    final Matcher matcher = pattern.matcher(lVideoUrl);
                    matcher.find();
                    lVideoUrl = matcher.group(1);
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getEstreamToVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public String getStreamangoVideo(String url) {
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(mUserAgent)
                    .referrer("http://www.google.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (!url.contains("embed")) {
                eles = docdata.select("div[class=leftcol]").select("iframe");
                if (eles != null && !eles.isEmpty()) {
                    url = eles.get(0).attr("src");
                    return getMP4UploadComVideo(url);
                }
            } else {
                eles = docdata.select("div[class=videocontainer] + script");
                Pattern p = Pattern.compile("http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);

                String innerHtml = "";
                String videoUrl = "";
                for (int i = 0; i < eles.size(); i++) {
                    innerHtml = eles.get(i).html();
                    String[] innerHtmlSplit = innerHtml.split("\n");
                    for (String s : innerHtmlSplit) {
                        //https://streamango.com/v/d/lnorrkmcdnacnqsa~1500704504~108.21.0.0~uJnSFqaq/720
                        if (!s.contains("video/mp4"))
                            continue;
                        s = s.replace("srces.push({type:\"video/mp4\",src:\"", "https:").trim();
                        lVideoUrl = s.substring(0, s.lastIndexOf("\""));
                        break;
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    private String getYourUploadVideo(String url) {
        if (url.contains(".cdn."))
            return url;
        String lVideoUrl = "";
        try {
            Document docdata = Jsoup.connect(url).userAgent(getUserAgent())
                    .referrer("https://www.yourupload.com")
                    .timeout(Parser.getParseTimeOut()).get();
            Elements eles = null;
            if (url.contains("embed")) {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            } else {
                eles = docdata.select("video").select("source");
                for (Element element : eles) {
                    if (element.hasAttr("type") && element.attr("type").startsWith("video")) {
                        return element.attr("abs:src");
                    }
                }
            }
        } catch (IOException e) {
            WriteLog.appendLog(Log.getStackTraceString(e));
        }
        return lVideoUrl;
    }

    public static boolean isValidSource(Parser lParser) {
        return true;
        //return !lParser.getIsPremium();
    }

    public Document load(String url) throws IOException {
        Request request = new Request.Builder().url(url)
                .build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ": load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document load(String url, boolean userCookies) throws IOException {
        Request request = new Request.Builder().url(url)
                .header("User-Agent", getUserAgent())
                .addHeader("Referer", getServerUrl()).build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ":load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document load(String url, String referral) throws IOException {
        Request request = new Request.Builder().url(url)
                .header("User-Agent", getUserAgent())
                .addHeader("Referer", referral).build();
        Response response = client.newCall(request).execute();
        if (response.isSuccessful())
            return parseHtml(response.body().string(), response.request().url().toString());
        else {
            WriteLog.appendLog(name + ":load(" + url + ") didn't respond with a proper response " + response.code());
            return loadDocument(url);
        }
    }

    public Document loadDocument(String pURL) throws IOException {
        Document docdata = Jsoup.connect(pURL).userAgent(TextUtils.isEmpty(mCustomUserAgent) ? mUserAgent : mCustomUserAgent).referrer("http://www.google.com").timeout(mParseTimeOut).get();
        return docdata;
    }

    public Document parseHtml(String body, String url) {
        return Jsoup.parse(body, url);
    }

    public Map<String, String> getCategories() {
        return Collections.EMPTY_MAP;
    }
} public class BuildUtils {

    public static boolean isFroyoOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    public static boolean isGingerbreadOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
    }

    public static boolean isHoneycombOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean isHoneycombMR2OrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2;
    }

    public static boolean isIceCreamSandwichOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    }

    public static boolean isJellyBeanOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    public static boolean isJellyBeanMR2OrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
    }

    public static boolean isKitKatOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    }

    public static boolean isLollipopOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    public static boolean isMarshmallowOrLater() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    public static void setBackground(View view, Drawable d) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            view.setBackground(d);
        } else {
            view.setBackgroundDrawable(d);
        }
    }

} public class Constants {

    public static final int ANIME_COMPLETED = 1;

    public static class RequestCodes {
        public static final int LOGIN = 71245;
    }

    public static class ProfileCodes {
        public static final int PROFILE_ADD_ACCOUNT = 13200;
        public static final int PROFILE_VIEW = 13202;
        public static final int PROFILE_LOGOUT = 13203;
    }

    public static class Intents {
        public static final String ANIME_UPDATED = BuildConfig.APPLICATION_ID + ".ANIME_UPDATED";
        public static final String EPISODE_PARSED = BuildConfig.APPLICATION_ID + ".EPISODE_PARSED";
        public static final String EPISODE_DOWNLOAD = BuildConfig.APPLICATION_ID + ".EPISODE_DOWNLOAD";
    }

    public static class DownloadIntents {

        public static final String TYPE = "type";
        public static final String SUBTYPE = "subtype";
        public static final String PROCESS_SPEED = "process_speed";
        public static final String PROCESS_PROGRESS = "process_progress";
        public static final String URL = "url";
        public static final String TITLE = "mTitle";
        public static final String PATH = "path";
        public static final String FILENAME = "filename";
        public static final String ERROR_CODE = "error_code";
        public static final String ERROR_INFO = "error_info";
        public static final String IS_PAUSED = "is_paused";

        public class Types {

            public static final int PROCESS = 0;
            public static final int COMPLETE = 1;

            public static final int START = 2;
            public static final int PAUSE = 3;
            public static final int DELETE = 4;
            public static final int CONTINUE = 5;
            public static final int ADD = 6;
            public static final int STOP = 7;
            public static final int ERROR = 9;

            public static final int PAUSE_ALL = 10;
            public static final int DELETE_ALL = 11;
            public static final int CONTINUE_ALL = 12;
            public static final int COMPLETE_ALL = 13;
        }

        public class SubTypes {
            public static final int DOWNLOADING = 0;
            public static final int COMPLETED = 1;
            public static final int QUEUED = 2;
            public static final int PAUSED = 3;
        }
    }

    public static final int HOME_SCREEN_CATALOG = 0;
    public static final int HOME_SCREEN_LIBRARY = 1;
    public static final int HOME_SCREEN_FAVORITES = 2;
    public static final int HOME_SCREEN_HISTORY = 3;

    // App Related
    public static final String KEY_USER_AGREEMENT = "user_agreement";
    public static final String KEY_SOURCE_FILTERS = "source_filter";
    public static final String KEY_ANIME_SOURCE = "anime_source";
    public static final String KEY_ANIME_SOURCE_URL = "anime_source_url";
    public static final String KEY_ANIME_SOURCE_LANG = "anime_source_lang";
    public static final String KEY_HOME_SCREEN_TYPE = "home_screen_type";
    public static final String KEY_SMART_HOME_SCREEN = "smart_home_screen";

    public static final String KEY_SYS_UNLOCK_CODE = "sys_unlock_code";
    public static final String KEY_SEARCH_FILTER_BY_LANG = "sys_search_filter_by_lang";

    public static final String USE_DEFAULT_ANIME_SOURCE = "use_default_anime_source";
    public static final int DEFAULT_ANIME_SOURCE = 0;

    public static final int DISPLAY_TYPE_GRID = 0;
    public static final int DISPLAY_TYPE_LIST_NO_IMAGES = 1;
    public static final int DISPLAY_TYPE_LIST = 2;
    public static final int DISPLAY_TYPE_LIST_LARGE = 3;
    public static final String KEY_GRID_COLUMN_COUNT = "grid_column_count";
    public static final String KEY_LIST_COLUMN_COUNT = "list_column_count";
    public static final String KEY_SHOW_CATALOG_AS_GRID = "catalog_as_grid";
    public static final String KEY_SHOW_FAVORITES_AS_GRID = "favorites_as_grid";

    public static final String KEY_USE_SERVER_DATABASE = "use_server_database";
    public static final String KEY_PARSE_SERVER = "parse_server";

    public static final String LOAD_TYPE = "load_type";
    public static final String GENRE_TYPE = "genre_type";
    public static final String DIRECTORY_URL = "directory_url";

    public static final String KEY_EPISODE_DEFAULT_SORT = "episode_default_sort";
    public static final String KEY_EPISODE_HISTORY = "episode_history";
    public static final String KEY_EPISODE_VIEWED_COLOR = "episode_viewed_color";
    public static final String KEY_EPISODE_UNVIEWED_COLOR = "episode_unviewed_color";
    public static final String KEY_EPISODE_LAST_VIEWED_COLOR = "episode_last_viewed_color";
    public static final String KEY_EPISODE_SORT_BEFORE_DOWNLOAD = "episode_sort_before_download";
    public static final String KEY_EPISODE_SHOW_WARNING = "episode_show_warning";

    // Library Specific
    public static final String KEY_LIBRARY_DISPLAY_TYPE = "library_display_type";
    public static final String KEY_LIBRARY_FORCE2SD = "library_data_extsd";
    public static final String KEY_LIBRARY_PATH = "library_path";
    public static final String KEY_LIBRARY_SHOW_COVERS = "library_show_covers";
    public static final String KEY_LIBRARY_SHOW_DATA = "library_show_data";
    public static final String KEY_LIBRARY_SHOW_BACKUPS = "library_show_backups";

    // Favorites Specific
    public static final String KEY_FAVORITES_SORT = "favorites_sort";
    public static final String KEY_FAVORITES_DISPLAY_TYPE = "favorites_display_type";
    public static final String KEY_FAVORITES_UPDATE_NOTIFICATION = "favorites_update_notification";

    public static final String KEY_DOWNLOAD_WIFI_ONLY = "sys_wifi_download";

    public static boolean USE_RGB565 = false;
    public static final String KEY_LOADER_TYPE = "loader_type";
    public static final String KEY_LOADER_IMAGE_QUALITY_HIGH = "loader_image_quality_high";

    public static final String ANIME_ID = "anime_id";
    public static final String ANIME_TITLE = "anime_title";
    public static final String ANIME_URL = "anime_url";
    public static final String ANIME_PATH = "anime_path";
    public static final String EPISODE_ID = "episode_id";
    public static final String EPISODE_TITLE = "episode_title";
    public static final String EPISODE_URL = "episode_url";
    public static final String EPISODE_PATH = "episode_path";
    public static final String EPISODE_VIDEO_URL = "episode_video_url";
    public static final String EPISODE_POSITION = "episode_position";
    public static final String EPISODE_START_POSITION = "episode_startPosition";

    public static final String ANIME_PREV_PATH_LIST = "anime_prev_path_list";

    public static final int UPDATE_NOTIFICATION_NONE = 0;
    public static final int UPDATE_NOTIFICATION_ONCE = 1;
    public static final int UPDATE_NOTIFICATION_PER_MANGA = 2;


    public static final int RibbonDrawable[] = {
            R.drawable.ribbon0,
            R.drawable.ribbon1,
            R.drawable.ribbon2,
            R.drawable.ribbon3,
            R.drawable.ribbon4,
            R.drawable.ribbon5,
            R.drawable.ribbon6,
            R.drawable.ribbon7,
            R.drawable.ribbon8,
            R.drawable.ribbon9,
            R.drawable.ribbon10,
            R.drawable.ribbon11,
            R.drawable.ribbon12,
            R.drawable.ribbon13,
            R.drawable.ribbon14

    };

    public static final int SELECT_SINGLE = 0;
    public static final int SELECT_MULTIPLE = 1;
    public static final int SELECT_ALL = 2;

    public static final String[] SELECTION_CHOICES = {
            "Single",
            "Multiple",
            "All"
    };
} public class NetworkUtils {

    /**
     * Pings a HTTP URL. This effectively sends a HEAD request and returns <code>true</code> if the response code is in
     * the 200-399 range.
     *
     * @param url     The HTTP URL to be pinged.
     * @param timeout The timeout in millis for both the connection timeout and the response read timeout. Note that
     *                the total timeout is effectively two times the given timeout.
     * @return <code>true</code> if the given HTTP URL has returned response code 200-399 on a HEAD request within the
     * given timeout, otherwise <code>false</code>.
     */
    public static boolean ping(String url, int timeout) {
        url = url.replaceFirst("https", "http"); // Otherwise an exception may be thrown on invalid SSL certificates.

        try {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            if (BuildUtils.isHoneycombOrLater()) {
                connection.setRequestProperty("Connection", "close");
            }
            connection.setRequestProperty("Accept-Encoding", "");
            connection.setConnectTimeout(timeout);
            connection.setReadTimeout(timeout);
            connection.setRequestMethod("HEAD");
            int responseCode = connection.getResponseCode();
            return (200 <= responseCode && responseCode <= 399);
        } catch (IOException exception) {
            exception.printStackTrace();
            return false;
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    public static boolean isWiFiConnected(Context context) {
        ConnectivityManager connMgr =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
        if (activeInfo != null && activeInfo.isConnected()) {
            return activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
        } else {
            return false;
        }
    }

    public static boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivity = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity == null) {
            return false;
        } else {
            NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo();
            boolean isConnected = activeNetwork != null &&
                    activeNetwork.isConnectedOrConnecting();
            WriteLog.appendLogIf(!isConnected, "Network is not connected");
            return isConnected;
        }

    }

    public static String getFileNameFromUrl(String url) {
        int index = url.lastIndexOf('?');
        String filename;
        if (index > 1) {
            filename = url.substring(url.lastIndexOf('/') + 1, index);
        } else {
            filename = url.substring(url.lastIndexOf('/') + 1);
        }

        if (filename == null || "".equals(filename.trim())) {
            filename = UUID.randomUUID() + ".apk";
        }
        return filename;
    }
} public class ParseManager implements DataRepository {

    private static String TAG = ParseManager.class.getSimpleName();
    private static ParseManager mInstance;

    public static ParseManager getInstance(Context context) {
        if (TextUtils.isEmpty(context.getString(R.string.parse_server)) || TextUtils.isEmpty(context.getString(R.string.parse_app_id))) {

        } else {
            if (mInstance == null)
                mInstance = new ParseManager(context);
        }
        return mInstance;
    }

    private ParseManager(Context context) {
        loadParseConfig(context);
    }

    private void loadParseConfig(Context context) {
        ParseObject.registerSubclass(AnimeRecord.class);
        ParseObject.registerSubclass(EpisodeRecord.class);
        Parse.initialize(new Parse.Configuration.Builder(context)
                .applicationId(context.getString(R.string.parse_app_id))
                .clientKey("")
                .server(context.getString(R.string.parse_server))
                .enableLocalDataStore()
                .build()
        );
    }

    public List<Anime> getAnimeList(int page) {
        ArrayList<Anime> animeList = new ArrayList<>();
        try {
            ParseQuery<AnimeRecord> query = AnimeRecord.getQuery();
            query.setSkip((page-1) * 100);
            for (AnimeRecord record : query.find()) {
                Anime anime = AnimeRecord.toAnime(record);
                if (anime != null)
                    animeList.add(anime);
            }
        } catch (ParseException e) {
            WriteLog.appendLogException(TAG, "getAnimeList failed", e);
        }
        return animeList;
    }

    @Override
    public List<Anime> getAnimeList() {
        ArrayList<Anime> animeList = new ArrayList<>();
        int limit = 10000;
        int page = 0;
        try {
            boolean endReached = false;
            do {
                ParseQuery<AnimeRecord> query = AnimeRecord.getQuery();
                query.setLimit(limit);
                query.setSkip(page * limit);
                List<AnimeRecord> list = query.find();
                if (list != null) {
                    for (AnimeRecord animeRecord : list) {
                        Anime anime = AnimeRecord.toAnime(animeRecord);
                        if (anime != null)
                            animeList.add(anime);
                    }
                    if (list.size() < limit)
                        endReached = true;
                    else
                        page++;
                } else
                    endReached = true;
            } while (!endReached);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        WriteLog.appendLog(TAG,"Anime loaded from server: " + animeList.size());
        return animeList;
    }

    @Override
    public HashMap<String, Anime> getAnimeMapByUrl(String url) {
        return null;
    }

    @Override
    public List<Anime> getAnimeListByUrl(String url) {
        ArrayList<Anime> animeList = new ArrayList<>();
        ParseQuery<AnimeRecord> query = AnimeRecord.getQuery();
        try {
            List<AnimeRecord> animeRecords = query.whereContains("url", url).find();
            for (AnimeRecord record : animeRecords) {
                Anime anime = AnimeRecord.toAnime(record);
                if (anime != null)
                    animeList.add(anime);
            }
        } catch (ParseException e) {
            WriteLog.appendLogException(TAG, "getAnimeListByUrl(" + url + ") failed", e);
        }
        return animeList;
    }

    @Override
    public List<Anime> getAnimeListByTitle(String title) {
        ArrayList<Anime> animeList = new ArrayList<>();
        ParseQuery<AnimeRecord> query = AnimeRecord.getQuery();
        try {
            List<AnimeRecord> animeRecords = query.whereContains("title", title).find();
            for (AnimeRecord record : animeRecords) {
                Anime anime = AnimeRecord.toAnime(record);
                if (anime != null)
                    animeList.add(anime);
            }
        } catch (ParseException e) {
            WriteLog.appendLogException(TAG, "getAnimeListByTitle(" + title + ") failed", e);
        }
        return animeList;
    }

    @Override
    public List<Anime> getAnimeListByTitle(String title, String url) {
        return null;
    }

    @Override
    public void insertAnimeList(List<Anime> animeList) {

    }

    @Override
    public void updateAnimeList(List<Anime> animeList) {

    }

    @Override
    public Anime getAnimeByUrl(String animeUrl) {
        return null;
    }

    @Override
    public Anime getAnimeByUrl(String animeUrl, boolean loadEpisodes) {
        return null;
    }

    @Override
    public String getCoverByUrl(String animeUrl) {
        return null;
    }

    @Override
    public void insertAnime(Anime anime) {

    }

    @Override
    public void updateAnime(Anime anime) {

    }

    @Override
    public void deleteAnime(String animeUrl) {

    }

    @Override
    public void insertEpisodeList(List<Episode> episodes) {

    }

    @Override
    public void deleteEpisodeList(Anime anime) {

    }

    @Override
    public void deleteEpisodes(String animeUrl) {

    }

    @Override
    public void updateEpisode(Episode episode) {

    }

    @Override
    public List<Episode> getEpisodes(String animeUrl) {
        return null;
    }

    @Override
    public Episode getEpisodeByUrl(String episodeUrl) {
        return null;
    }

    @Override
    public int getEpisodeCount(String animeUrl) {
        return 0;
    }

    @Override
    public String getEpisodeTitle(String url) {
        return null;
    }

    @Override
    public String getLastViewedEpisode(String animeUrl) {
        return null;
    }

    @Override
    public List<FavoriteRecord> getFavorites() {
        return null;
    }

    @Override
    public void insertFavorite(String animeUrl, int tagId) {

    }

    @Override
    public void deleteFavorite(String animeUrl) {

    }

    @Override
    public FavoriteTag getFavoriteTag(int id) {
        return null;
    }

    @Override
    public List<FavoriteTag> getFavoriteTags() {
        return null;
    }

    @Override
    public void insertFavoriteTag(int tagId, String title, int ribbonId) {

    }

    @Override
    public boolean deleteFavoriteTag(int tagId) {
        return false;
    }

    @Override
    public FavoriteRecord getFavoriteByAnimeUrl(String url) {
        return null;
    }

    @Override
    public boolean isFavorite(String animeUrl) {
        return false;
    }

    @Override
    public void deleteFavoriteRecordsByTagID(int tagId) {

    }

    @Override
    public boolean deleteFavoriteRecords() {
        return false;
    }

    @Override
    public void insertHistoryRecord(String animeUrl, String episodeUrl, boolean isViewed) {

    }

    @Override
    public HistoryRecord getHistoryRecord(String episodeUrl) {
        return null;
    }

    @Override
    public void deleteHistoryRecord(String episodeUrl) {

    }

    @Override
    public void deleteHistoryRecords() {

    }

    @Override
    public List<HistoryRecord> getHistoryRecords() {
        return null;
    }

    @Override
    public List<HistoryRecord> getHistoryRecords(String animeUrl) {
        return null;
    }

    @Override
    public List<DownloadRecord> getDownloadTasks(boolean isCompleted) {
        return null;
    }

    @Override
    public DownloadRecord getDownloadTask(String episodeUrl, boolean isCompleted) {
        return null;
    }

    @Override
    public void deleteDownloadTask(String episodeUrl) {

    }

    @Override
    public void deleteIncompleteDownloadTasks() {

    }

    @Override
    public DownloadRecord getDownloadTask(String episodeUrl) {
        return null;
    }

    @Override
    public void insertDownloadTask(DownloadRecord record) {

    }

    @Override
    public void updateDownloadTask(DownloadRecord record) {

    }

    @Override
    public void deleteOfflineHistoryRecord(String path) {

    }

    @Override
    public void deleteOfflineHistoryRecords() {

    }

    @Override
    public OfflineHistoryRecord getOfflineHistoryRecord(String path) {
        return null;
    }

    @Override
    public List<OfflineHistoryRecord> getOfflineHistoryRecords() {
        return null;
    }

    @Override
    public void insertOfflineHistoryRecord(String path, boolean isViewed) {

    }

    @Override
    public Boolean isPathViewed(String path) {
        return null;
    }

    @Override
    public String getLastViewedByPath(String parent) {
        return null;
    }

    public long getAnimeCount() {
        if (mInstance == null)
            return 0;
        ParseQuery<AnimeRecord> query = AnimeRecord.getQuery();
        try {
            return query.count();
        } catch (ParseException e) {

        }
        return 0;
    }

    @Override
    public HashMap<String, Anime> getAnimeMap() {
        return null;
    }
} public class WriteLog {
    public static final int VERBOSE = 0;
    public static final int DEBUG = 1;
    private static boolean mIsEnabled = false;
    public static boolean mSystemOut = true;
    public static boolean mPrintPhoneInfo = false;
    public static int mDebugLevel = 0;
    private static Context mContext;

    public static void setContext(Context context) {
        mContext = context;
    }

    public static void setEnabled(boolean isEnabled) {
        mIsEnabled = isEnabled;
        if (mIsEnabled)
            mDebugLevel = DEBUG;
    }

    public final static String getDateTime() {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd_hh:mm:ss");
        return df.format(new Date());
    }

    public static String getPhoneInfo() {
        mPrintPhoneInfo = true;
        String s = getDateTime() + " Phone Info:";
        s += "\n OS Version: " + System.getProperty("os.version") + "(" + android.os.Build.VERSION.INCREMENTAL + ")";
        s += "\n OS API Level: " + android.os.Build.VERSION.SDK_INT;
        s += "\n Device: " + android.os.Build.DEVICE;
        s += "\n Model (and Product): " + android.os.Build.MODEL + " (" + android.os.Build.PRODUCT + ")";
        s += "\n Screen Size Height: " + DisplayUtils.getScreenHeight(mContext) + "  Width: " + DisplayUtils.getScreenWidth(mContext);
        return s;
    }

    public static void appendLog(String... text) {
        if (text != null && text.length > 0) {
            String parsedText = text[0];
            for (int i = 0; i < text.length - 1; i++) {
                if (i + 1 >= text.length)
                    break;
                parsedText = parsedText.replaceAll("\\{" + i + "\\}", text[i + 1]);
            }
            appendLog(parsedText);
        }
    }

    public static void appendLog(String tag, String text) {
        if (mSystemOut)
            Log.d(tag, text);
        printToFile(tag + ": " + text);
    }

    public static void appendLog(String tag, String text, int level) {
        if (level >= mDebugLevel) {
            if (mSystemOut)
                Log.d(tag, text);
            printToFile(tag + ": " + text);
        }
    }

    private static void printToFile(String text) {
        if (mIsEnabled) {
            File logFile = new File(StorageUtils.getDataDirectory(), "Log.txt");
            if (!logFile.exists()) {
                try {
                    logFile.createNewFile();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            try {
                //BufferedWriter for performance, true to set append to file flag
                BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
                if (!mPrintPhoneInfo)
                    buf.append(getPhoneInfo());
                buf.append("[" + getDateTime() + "] " + text);
                buf.newLine();
                buf.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static void appendLog(String text) {
        if (mSystemOut) {
            System.out.println(text);
        }
        printToFile(text);
    }

    public static void appendLogIf(boolean condition, String text) {
        if (condition)
            appendLog(text);
    }

    public static void appendLogDetailedOnly(String text) {
        if (isDetailed())
            appendLog(text);
    }

    public static void appendLogDetailedOnly(String tag, String text) {
        if (isDetailed())
            appendLog(tag, text);
    }

    public static void appendLogException(String tag, String text, Exception exception) {
        WriteLog.appendLog(tag, text);
        WriteLog.appendLog(Log.getStackTraceString(exception));
    }

    public static void appendLogException(String text, Exception exception) {
        WriteLog.appendLog(text);
        WriteLog.appendLog(Log.getStackTraceString(exception));
    }

    public static boolean isDetailed() {
        return mDebugLevel > 1;
    }

    public static boolean IsEnabled() {
        return mIsEnabled;
    }
} public class GridRecyclerView extends FastScrollRecyclerView {

    private View emptyView;
    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {


        @Override
        public void onChanged() {
            Adapter<?> adapter = getAdapter();
            if (adapter != null && emptyView != null) {
                if (adapter.getItemCount() == 0) {
                    emptyView.setVisibility(View.VISIBLE);
                    GridRecyclerView.this.setVisibility(View.GONE);
                } else {
                    emptyView.setVisibility(View.GONE);
                    GridRecyclerView.this.setVisibility(View.VISIBLE);
                }
            }

        }
    };

    public GridRecyclerView(Context context) {
        super(context);
    }

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

    public GridRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setLayoutManager(RecyclerView.LayoutManager layout) {
        if (!isInEditMode())
            if (layout instanceof GridLayoutManager) {
                super.setLayoutManager(layout);
            } else {
                throw new ClassCastException("You should only use a GridLayoutManager with GridRecyclerView.");
            }
    }

    @Override
    protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params, int index, int count) {
        if (!isInEditMode())
            if (getAdapter() != null && getLayoutManager() instanceof GridLayoutManager) {

                GridLayoutAnimationController.AnimationParameters animationParams =
                        (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

                if (animationParams == null) {
                    animationParams = new GridLayoutAnimationController.AnimationParameters();
                    params.layoutAnimationParameters = animationParams;
                }

                int columns = ((GridLayoutManager) getLayoutManager()).getSpanCount();

                animationParams.count = count;
                animationParams.index = index;
                animationParams.columnsCount = columns;
                animationParams.rowsCount = count / columns;

                final int invertedIndex = count - 1 - index;
                animationParams.column = columns - 1 - (invertedIndex % columns);
                animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns;

            } else {
                super.attachLayoutAnimationParameters(child, params, index, count);
            }
    }

    @Override
    public boolean isShown() {
        return super.isShown() && getVisibility() == VISIBLE;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        if (getAdapter() != null)
            getAdapter().unregisterAdapterDataObserver(emptyObserver);
        super.setAdapter(adapter);

        if (adapter != null) {
            adapter.registerAdapterDataObserver(emptyObserver);
        }

        emptyObserver.onChanged();
    }

    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
    }
} public class ThemeManager {
    private static final String KEY_THEME = "theme";
    private static final String KEY_BG_THEME = "bg_theme";
    private static ThemeManager mInstance = null;

    private ThemeType currentTheme = ThemeType.Light;
    private boolean isLightBackground = true;

    public void setCurrentTheme(AppCompatActivity activity) {
        SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
        int themeType = Integer.parseInt(mPrefs.getString(KEY_THEME, String.valueOf(ThemeType.Blue.getValue())));
        boolean isLight = mPrefs.getBoolean(KEY_BG_THEME, false);
        setCurrentTheme(activity, themeType, isLight);
    }

    public enum ThemeType {
        Light(0),
        Dark(1),
        Amber(2),
        Blue(3),
        Blue_Grey(4),
        Brown(5),
        Cyan(6),
        Deep_Orange(7),
        Deep_Purple(8),
        Green(9),
        Indigo(10),
        Light_Blue(11),
        Light_Green(12),
        Lime(13),
        Orange(14),
        Pink(15),
        Purple(16),
        Red(17),
        Teal(18),
        Yellow(19);

        private int value;

        ThemeType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    private int mPrimaryColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_primary,
            R.color.material_blue_primary,
            R.color.material_blue_grey_primary,
            R.color.material_brown_primary,
            R.color.material_cyan_primary,
            R.color.material_deep_orange_primary,
            R.color.material_deep_purple_primary,
            R.color.material_green_primary,
            R.color.material_indigo_primary,
            R.color.material_light_blue_primary,
            R.color.material_light_green_primary,
            R.color.material_lime_primary,
            R.color.material_orange_primary,
            R.color.material_pink_primary,
            R.color.material_purple_primary,
            R.color.material_red_primary,
            R.color.material_teal_primary,
            R.color.material_yellow_primary
    };

    public int mPrimaryDarkColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_dark,
            R.color.material_blue_dark,
            R.color.material_blue_grey_dark,
            R.color.material_brown_dark,
            R.color.material_cyan_dark,
            R.color.material_deep_orange_dark,
            R.color.material_deep_purple_dark,
            R.color.material_green_dark,
            R.color.material_indigo_dark,
            R.color.material_light_blue_dark,
            R.color.material_light_green_dark,
            R.color.material_lime_dark,
            R.color.material_orange_dark,
            R.color.material_pink_dark,
            R.color.material_purple_dark,
            R.color.material_red_dark,
            R.color.material_teal_dark,
            R.color.material_yellow_dark
    };

    private int mAccentColors[] = {
            R.color.holo_blue_light,
            R.color.holo_blue_dark,
            R.color.material_amber_accent,
            R.color.material_blue_accent,
            R.color.material_blue_grey_accent,
            R.color.material_brown_accent,
            R.color.material_cyan_accent,
            R.color.material_deep_orange_accent,
            R.color.material_deep_purple_accent,
            R.color.material_green_accent,
            R.color.material_indigo_accent,
            R.color.material_light_blue_accent,
            R.color.material_light_green_accent,
            R.color.material_lime_accent,
            R.color.material_orange_accent,
            R.color.material_pink_accent,
            R.color.material_purple_accent,
            R.color.material_red_accent,
            R.color.material_teal_accent,
            R.color.material_yellow_accent
    };

    private ThemeManager() {
    }

    public static ThemeManager getInstance(Context ctx) {
        /**
         * use the application context as suggested by CommonsWare. this will
         * ensure that you dont accidentally leak an Activitys context (see this
         * article for more information:
         * http://developer.android.com/resources/articles
         * /avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new ThemeManager();
        }
        return mInstance;
    }

    public static ThemeManager getInstance() {
        /**
         * use the application context as suggested by CommonsWare. this will
         * ensure that you dont accidentally leak an Activitys context (see this
         * article for more information:
         * http://developer.android.com/resources/articles
         * /avoiding-memory-leaks.html)
         */
        return mInstance;
    }

    public void setCurrentTheme(int theme) {
        switch (theme) {
            default:
                currentTheme = ThemeType.values()[theme];
                break;
        }
    }

    public void setCurrentTheme(Activity act, int theme, boolean isLight) {
        isLightBackground = isLight;
        if (!isLight) {
            switch (ThemeType.values()[theme]) {
                case Dark:
                    isLightBackground = false;
                    currentTheme = ThemeType.Dark;
                    act.setTheme(R.style.DarkTheme);
                    break;
                case Light:
                    currentTheme = ThemeType.Light;
                    act.setTheme(R.style.LightTheme);
                    break;
                case Amber:
                    currentTheme = ThemeType.Amber;
                    act.setTheme(R.style.AmberTheme);
                    break;
                case Blue:
                    currentTheme = ThemeType.Blue;
                    act.setTheme(R.style.BlueTheme);
                    break;
                case Blue_Grey:
                    currentTheme = ThemeType.Blue_Grey;
                    act.setTheme(R.style.BlueGreyTheme);
                    break;
                case Brown:
                    currentTheme = ThemeType.Brown;
                    act.setTheme(R.style.BrownTheme);
                    break;
                case Cyan:
                    currentTheme = ThemeType.Cyan;
                    act.setTheme(R.style.CyanTheme);
                    break;
                case Deep_Orange:
                    currentTheme = ThemeType.Deep_Orange;
                    act.setTheme(R.style.DeepOrangeTheme);
                    break;
                case Deep_Purple:
                    currentTheme = ThemeType.Deep_Purple;
                    act.setTheme(R.style.DeepPurpleTheme);
                    break;
                case Green:
                    currentTheme = ThemeType.Green;
                    act.setTheme(R.style.GreenTheme);
                    break;
                case Indigo:
                    currentTheme = ThemeType.Indigo;
                    act.setTheme(R.style.IndigoTheme);
                    break;
                case Light_Blue:
                    currentTheme = ThemeType.Light_Blue;
                    act.setTheme(R.style.LightBlueTheme);
                    break;
                case Light_Green:
                    currentTheme = ThemeType.Light_Green;
                    act.setTheme(R.style.LightGreenTheme);
                    break;
                case Lime:
                    currentTheme = ThemeType.Lime;
                    act.setTheme(R.style.LimeTheme);
                    break;
                case Orange:
                    currentTheme = ThemeType.Orange;
                    act.setTheme(R.style.OrangeTheme);
                    break;
                case Pink:
                    currentTheme = ThemeType.Pink;
                    act.setTheme(R.style.PinkTheme);
                    break;
                case Purple:
                    currentTheme = ThemeType.Purple;
                    act.setTheme(R.style.PurpleTheme);
                    break;
                case Red:
                    currentTheme = ThemeType.Red;
                    act.setTheme(R.style.RedTheme);
                    break;
                case Teal:
                    currentTheme = ThemeType.Teal;
                    act.setTheme(R.style.TealTheme);
                    break;
                case Yellow:
                    currentTheme = ThemeType.Yellow;
                    act.setTheme(R.style.YellowTheme);
                    break;
            }
        } else {
            switch (ThemeType.values()[theme]) {
                case Dark:
                    isLightBackground = false;
                    currentTheme = ThemeType.Dark;
                    act.setTheme(R.style.DarkTheme);
                    break;
                case Light:
                    currentTheme = ThemeType.Light;
                    act.setTheme(R.style.LightTheme);
                    break;
                case Amber:
                    currentTheme = ThemeType.Amber;
                    act.setTheme(R.style.AmberThemeL);
                    break;
                case Blue:
                    currentTheme = ThemeType.Blue;
                    act.setTheme(R.style.BlueThemeL);
                    break;
                case Blue_Grey:
                    currentTheme = ThemeType.Blue_Grey;
                    act.setTheme(R.style.BlueGreyThemeL);
                    break;
                case Brown:
                    currentTheme = ThemeType.Brown;
                    act.setTheme(R.style.BrownThemeL);
                    break;
                case Cyan:
                    currentTheme = ThemeType.Cyan;
                    act.setTheme(R.style.CyanThemeL);
                    break;
                case Deep_Orange:
                    currentTheme = ThemeType.Deep_Orange;
                    act.setTheme(R.style.DeepOrangeThemeL);
                    break;
                case Deep_Purple:
                    currentTheme = ThemeType.Deep_Purple;
                    act.setTheme(R.style.DeepPurpleThemeL);
                    break;
                case Green:
                    currentTheme = ThemeType.Green;
                    act.setTheme(R.style.GreenThemeL);
                    break;
                case Indigo:
                    currentTheme = ThemeType.Indigo;
                    act.setTheme(R.style.IndigoThemeL);
                    break;
                case Light_Blue:
                    currentTheme = ThemeType.Light_Blue;
                    act.setTheme(R.style.LightBlueThemeL);
                    break;
                case Light_Green:
                    currentTheme = ThemeType.Light_Green;
                    act.setTheme(R.style.LightGreenThemeL);
                    break;
                case Lime:
                    currentTheme = ThemeType.Lime;
                    act.setTheme(R.style.LimeThemeL);
                    break;
                case Orange:
                    currentTheme = ThemeType.Orange;
                    act.setTheme(R.style.OrangeThemeL);
                    break;
                case Pink:
                    currentTheme = ThemeType.Pink;
                    act.setTheme(R.style.PinkThemeL);
                    break;
                case Purple:
                    currentTheme = ThemeType.Purple;
                    act.setTheme(R.style.PurpleThemeL);
                    break;
                case Red:
                    currentTheme = ThemeType.Red;
                    act.setTheme(R.style.RedThemeL);
                    break;
                case Teal:
                    currentTheme = ThemeType.Teal;
                    act.setTheme(R.style.TealThemeL);
                    break;
                case Yellow:
                    currentTheme = ThemeType.Yellow;
                    act.setTheme(R.style.YellowThemeL);
                    break;
            }
        }
    }

    public ThemeType getCurrentTheme() {
        return currentTheme;
    }

    public boolean isLightBackground() {
        return (currentTheme == ThemeType.Light) || isLightBackground && (currentTheme != ThemeType.Dark);
    }

    public int getTextColor() {
        switch (currentTheme) {
            case Light:
                return Color.BLACK;
            case Dark:
                return Color.WHITE;
            default:
                if (!isLightBackground)
                    return Color.WHITE;
                else
                    return Color.BLACK;
        }
    }

    public int getInvertedTextColor() {
        switch (currentTheme) {
            case Dark:
                return Color.BLACK;
            case Light:
                return Color.WHITE;
            default:
                if (isLightBackground)
                    return Color.WHITE;
                else
                    return Color.BLACK;
        }
    }

    public int getPrimaryTextColor(Context context) {
        switch (currentTheme) {
            case Light:
                return Color.BLACK;
            case Dark:
                return Color.WHITE;
            default:
                return context.getResources().getColor(mPrimaryColors[currentTheme.value]);
        }
    }

    public boolean isValidTextColor(int color) {
        switch (currentTheme) {
            case Light:
                return (color != Color.WHITE);
            case Dark:
                return (color != Color.BLACK);
            default:
                if (!isLightBackground)
                    return (color != Color.BLACK);
                else
                    return (color != Color.WHITE);
        }
    }

    public int getBackgroundColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.background_holo_light);
            case Dark:
                return context.getResources().getColor(R.color.background_holo_dark);
            default:
                if (!isLightBackground)
                    return context.getResources().getColor(R.color.background_floating_material_dark);
                else
                    return context.getResources().getColor(R.color.background_floating_material_light);
        }
    }

    public int getBackgroundColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return android.R.color.transparent;
        }
    }

    public int getIndicatorColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mPrimaryDarkColors[currentTheme.value]);
        }
    }

    public int getAccentColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return mAccentColors[currentTheme.value];
        }
    }

    public int getAccentColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mAccentColors[currentTheme.value]);
        }
    }

    public int getPrimaryDarkColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.background_holo_light;
            case Dark:
                return R.color.background_holo_dark;
            default:
                return mPrimaryDarkColors[currentTheme.value];
        }
    }

    public int getPrimaryDarkColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.holo_blue_light);
            case Dark:
                return context.getResources().getColor(R.color.holo_blue_dark);
            default:
                return context.getResources().getColor(mPrimaryDarkColors[currentTheme.value]);
        }
    }

    public int getPrimaryColorResId() {
        switch (currentTheme) {
            case Light:
                return R.color.primary_material_light;
            case Dark:
                return R.color.primary_dark_material_dark;
            default:
                return mPrimaryColors[currentTheme.value];
        }
    }

    public int getPrimaryColor(Context context) {
        switch (currentTheme) {
            case Light:
                return context.getResources().getColor(R.color.primary_material_light);
            case Dark:
                return context.getResources().getColor(R.color.primary_material_dark);
            default:
                return context.getResources().getColor(mPrimaryColors[currentTheme.value]);
        }
    }

} public static final int MODE_SINGLE = 1;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer.DrawableContainerState;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.SharedElementCallback;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnCloseListener;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.squareup.picasso.Picasso;
import com.taskdesignsinc.android.myanimeviewer.MAVApplication;
import com.taskdesignsinc.android.myanimeviewer.R;
import com.taskdesignsinc.android.myanimeviewer.adapter.AnimeRecyclerAdapter;
import com.taskdesignsinc.android.myanimeviewer.adapter.FavoriteListAdapter;
import com.taskdesignsinc.android.myanimeviewer.adapter.base.OnItemClickListener;
import com.taskdesignsinc.android.myanimeviewer.loader.AnimeListLoader;
import com.taskdesignsinc.android.myanimeviewer.model.Anime;
import com.taskdesignsinc.android.myanimeviewer.model.FavoriteTag;
import com.taskdesignsinc.android.myanimeviewer.model.helper.AnimeHelper;
import com.taskdesignsinc.android.myanimeviewer.parser.Parser;
import com.taskdesignsinc.android.myanimeviewer.util.BuildUtils;
import com.taskdesignsinc.android.myanimeviewer.util.Constants;
import com.taskdesignsinc.android.myanimeviewer.util.NetworkUtils;
import com.taskdesignsinc.android.myanimeviewer.util.ParseManager;
import com.taskdesignsinc.android.myanimeviewer.util.WriteLog;
import com.taskdesignsinc.android.myanimeviewer.view.GridRecyclerView;
import com.taskdesignsinc.android.thememanager.ThemeManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import static com.taskdesignsinc.android.myanimeviewer.adapter.base.SelectableAdapter.MODE_SINGLE;
package com.taskdesignsinc.android.myanimeviewer.fragment;






@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ParseAnimeListFragment extends Fragment
        implements OnQueryTextListener, OnCloseListener,
        LoaderManager.LoaderCallbacks<List<Anime>> {