public class DownloadListActivity extends SupportListActivity {

	private DownloadListAdapter mAdapter;
	private DownloadResultReceiver mReceiver;
	private SharedPreferences mPrefs;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
		ThemeManager.getInstance().setCurrentTheme(this);
		super.onCreate(savedInstanceState);

		setContentView(R.layout.download_list_activity);
		Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
		setSupportActionBar(toolbar);

		if (!StorageUtils.isSDCardPresent()) {
			Toast.makeText(this, "SD card not found.", Toast.LENGTH_LONG).show();
			return;
		}

		if (!StorageUtils.isSDCardWritable()) {
			Toast.makeText(this, "SD card not writable", Toast.LENGTH_LONG).show();
			return;
		}

		//downloadList = (ListView) findViewById(R.id.download_list);
		getSupportActionBar().setTitle(R.string.downloads);
		getSupportActionBar().setDisplayHomeAsUpEnabled(true);
		getSupportActionBar().setHomeButtonEnabled(true);
		getListView().setDivider(new ColorDrawable(ThemeManager.getInstance().getBackgroundColor(this)));
		mAdapter = new DownloadListAdapter(this);
		setListAdapter(mAdapter);

		if (mAdapter.isEmpty()) {
			TextView empty = (TextView) getListView().getEmptyView();
			empty.setText("Nothing is downloading right now");
		}

		startDownloadManager();

		// // handle intent
		// Intent intent = getIntent();
		// handleIntent(intent);
		mReceiver = new DownloadResultReceiver();
		IntentFilter filter = new IntentFilter();
		filter.addAction(Constants.Intents.EPISODE_DOWNLOAD);
		registerReceiver(mReceiver, filter);
	}
	
	private void startDownloadManager() {
		boolean lWifiOnly = mPrefs.getBoolean(Constants.KEY_DOWNLOAD_WIFI_ONLY, false);
		if ((!lWifiOnly
				&& NetworkUtils.isNetworkAvailable(this))
				|| (lWifiOnly
						&& NetworkUtils.isWiFiConnected(this))) {
			Intent downloadIntent = new Intent(this,
					DownloadService.class);
			downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.START);
			startService(downloadIntent);
		} else {
			Intent downloadIntent = new Intent(this,
					DownloadService.class);
			downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.STOP);
			startService(downloadIntent);
		}
	}

	@Override
	protected void onDestroy() {
		unregisterReceiver(mReceiver);
		super.onDestroy();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu pMenu) {
		pMenu.add("Continue All");
		pMenu.add("Pause All");
		pMenu.add("Delete All");
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem pItem) {
		if(pItem.getItemId() == android.R.id.home)
		{
			finish();
			return true;
		} else if (pItem.getTitle().equals("Continue All")) {
			Intent downloadIntent = new Intent(this,
					DownloadService.class);
			downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.CONTINUE_ALL);
			startService(downloadIntent);
			return true;
		} else if (pItem.getTitle().equals("Pause All")) {
			Intent downloadIntent = new Intent(this,
					DownloadService.class);
			downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.PAUSE_ALL);
			startService(downloadIntent);
			return true;
		} else if (pItem.getTitle().equals("Delete All")) {
			Intent downloadIntent = new Intent(this,
					DownloadService.class);
			downloadIntent.putExtra(Constants.DownloadIntents.TYPE, Constants.DownloadIntents.Types.DELETE_ALL);
			startService(downloadIntent);
			return true;
		}
		return super.onOptionsItemSelected(pItem);
	}

	public class DownloadResultReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {

			handleIntent(intent);

		}

		private void handleIntent(Intent intent) {

			if (intent != null
					&& intent.getAction().equals(
							Constants.Intents.EPISODE_DOWNLOAD)) {
				int type = intent.getIntExtra(Constants.DownloadIntents.TYPE, -1);
				int subtype = intent.getIntExtra(Constants.DownloadIntents.SUBTYPE, -1);
				String url;
				String title;

				switch (type) {
				case Constants.DownloadIntents.Types.ADD:
					url = intent.getStringExtra(Constants.DownloadIntents.URL);
					title = intent.getStringExtra(Constants.DownloadIntents.TITLE);
					if (title == null)
						title = MAVApplication.getInstance().getRepository().getEpisodeTitle(url);
					boolean isPaused = intent.getBooleanExtra(Constants.DownloadIntents.IS_PAUSED, false);
					if (!TextUtils.isEmpty(url)) {
						mAdapter.addItem(subtype, url, title, isPaused);
					}
					break;
				case Constants.DownloadIntents.Types.COMPLETE:
					url = intent.getStringExtra(Constants.DownloadIntents.URL);
					if (!TextUtils.isEmpty(url)) {
						mAdapter.removeItem(url);
					}
					break;
				case Constants.DownloadIntents.Types.PROCESS:
					url = intent.getStringExtra(Constants.DownloadIntents.URL);
					title = intent.getStringExtra(Constants.DownloadIntents.TITLE);
					if (title == null)
						title = MAVApplication.getInstance().getRepository().getEpisodeTitle(url);
					View taskListItem = getListView().findViewWithTag(url);
					DownloadListAdapter.ViewHolder viewHolder = new DownloadListAdapter.ViewHolder(taskListItem);
					viewHolder.setData(Constants.DownloadIntents.SubTypes.DOWNLOADING, url, title, intent.getStringExtra(Constants.DownloadIntents.PROCESS_SPEED),
							String.valueOf(intent.getLongExtra(Constants.DownloadIntents.PROCESS_PROGRESS, 0)));
					break;
				case Constants.DownloadIntents.Types.PAUSE:
					url = intent.getStringExtra(Constants.DownloadIntents.URL);
					if (!TextUtils.isEmpty(url)) {
						mAdapter.pauseItem(url);
					}
					break;
				case Constants.DownloadIntents.Types.ERROR:
					url = intent.getStringExtra(Constants.DownloadIntents.URL);
					// int errorCode =
					// intent.getIntExtra(DownloadIntents.ERROR_CODE,
					// DownloadTask.ERROR_UNKONW);
					// handleError(url, errorCode);
					break;
				case Constants.DownloadIntents.Types.COMPLETE_ALL:
					mAdapter.removeAllItems();
					break;
				default:
					break;
				}
			}
		}

		@SuppressWarnings("unused")
		private void showAlert(String title, String msg) {

			new AlertDialog.Builder(DownloadListActivity.this).setTitle(title).setMessage(msg)
			.setPositiveButton("OK", new DialogInterface.OnClickListener() {

				@Override
				public void onClick(DialogInterface dialog, int which) {

					dialog.dismiss();
				}
			}).create().show();
		}
	}
} 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);
                }
            }
        }
    }
} @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 Episode {

    @Id
    long id;
    @Relation
    Anime anime;
    int index;
    String title;
    String url;
    Date date;
    String videoUrl;
    boolean isViewed = false;
    String localPath;
    long playPosition = 0;
    String animeUrl;

    @Override
    public String toString() {
        return "Episode [name=" + title + ", "
                + "url=" + url + ", "
                + "animeUrl=" + animeUrl + ", "
                + "index=" + index + ", "
                + "videoUrl=" + videoUrl + ", "
                + "isViewed=" + isViewed + ", "
                + "date=" + date + ", "
                + "localPath=" + localPath + " "
                + "playPosition=" + localPath + " "
                + "]";
    }

    /** To-one relationship, resolved on first access. */
    @Generated(697412469)
    public Anime getAnime() {
        anime = animeToOne.getTarget();
        return anime;
    }

    /** Set the to-one relation including its ID property. */
    @Keep
    public void setAnime(Anime anime) {
        animeToOne.setTarget(anime);
        this.anime = anime;
        this.animeUrl = anime.getUrl();
    }

    public static Comparator<Episode> SortByPosition = new Comparator<Episode>() {

        public int compare(Episode pEpisode1, Episode pEpisode2) {

            int diff = pEpisode1.index - pEpisode2.index;

            // ascending order
            return diff;
        }
    };
    /** Used to resolve relations */
    @Internal
    @Generated(1307364262)
    transient BoxStore __boxStore;

    @Generated(122010498)
    @Internal
    /** This constructor was generated by ObjectBox and may change any time. */
    public Episode(long id, int index, String title, String url, Date date,
            String videoUrl, boolean isViewed, String localPath, long playPosition,
            String animeUrl, long animeId) {
        this.id = id;
        this.index = index;
        this.title = title;
        this.url = url;
        this.date = date;
        this.videoUrl = videoUrl;
        this.isViewed = isViewed;
        this.localPath = localPath;
        this.playPosition = playPosition;
        this.animeUrl = animeUrl;
        this.animeToOne.setTargetId(animeId);
    }

    @Generated(1336866052)
    public Episode() {
    }

    @Internal
    @Generated(1721299457)
    transient ToOne<Anime> animeToOne = new ToOne<>(this, Episode_.anime);

    public long getId() {
        return id;
    }

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

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    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 Date getDate() {
        return date;
    }

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

    public String getVideoUrl() {
        return videoUrl;
    }

    public void setVideoUrl(String videoUrl) {
        this.videoUrl = videoUrl;
    }

    public boolean isViewed() {
        return isViewed;
    }

    public void setViewed(boolean viewed) {
        isViewed = viewed;
    }

    public String getLocalPath() {
        return localPath;
    }

    public void setLocalPath(String localPath) {
        this.localPath = localPath;
    }

    public long getPlayPosition() {
        return playPosition;
    }

    public void setPlayPosition(long playPosition) {
        this.playPosition = playPosition;
    }

    public String getAnimeUrl() {
        return animeUrl;
    }

    public void setAnimeUrl(String animeUrl) {
        this.animeUrl = animeUrl;
    }
} 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 FileAlreadyExistException extends DownloadException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public FileAlreadyExistException(String message) {

        super(message);
    }

} public class NoMemoryException extends DownloadException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public NoMemoryException(String message) {

        super(message);
        // TODO Auto-generated constructor stub
    }

} public class AnimeUtils {

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

    public synchronized static void saveAsync(Anime... pAnime) {
        if (pAnime == null || pAnime.length == 0)
            return;
        AsyncTask<Anime, Void, Void> task = new AsyncTask<Anime, Void, Void>() {

            @Override
            protected Void doInBackground(Anime... params) {
                Anime lAnime = null;
                for (int i = 0; i < params.length; i++) {
                    lAnime = params[i];
                    if (lAnime == null)
                        continue;
                    File pFileName = new File(
                            StorageUtils.getDataDirectory(),
                            FileUtils.getValidFileName(lAnime.getTitle())
                                    + "/"
                                    + "anime.json");
                    if (!pFileName.getParentFile().exists())
                        continue;
                    try {
                        Gson gson = new Gson();
                        FileWriter writer = new FileWriter(pFileName.getAbsolutePath());
                        // convert java object to JSON format,
                        // and returned as JSON formatted string
                        String json = gson.toJson(lAnime);
                        WriteLog.appendLog(mTag, "saveAsync called" + pFileName.getAbsolutePath() + "\n" + json);
                        writer.write(json);
                        writer.close();
                    } catch (IOException e) {
                        WriteLog.appendLog(Log.getStackTraceString(e));
                        e.printStackTrace();
                    }
                }
                return null;
            }

        };
        AsyncTaskUtils.executeAsyncTask(task, pAnime);
    }

    public static void saveAsync(final Context context, Anime... pAnime) {
        if (pAnime == null || pAnime.length == 0)
            return;
        AsyncTask<Anime, Void, Void> task = new AsyncTask<Anime, Void, Void>() {

            @Override
            protected Void doInBackground(Anime... params) {
                Anime lAnime = null;
                for (int i = 0; i < params.length; i++) {
                    lAnime = params[i];
                    if (lAnime == null)
                        continue;
                    File lFileName = new File(
                            StorageUtils.getDataDirectory(),
                            FileUtils.getValidFileName(lAnime.getTitle())
                                    + "/"
                                    + "anime.json");
                    if (!lFileName.getParentFile().exists())
                        continue;
                    try {
                        Gson gson = new Gson();
                        FileWriter writer = new FileWriter(lFileName.getAbsolutePath());
                        // convert java object to JSON format,
                        // and returned as JSON formatted string
                        String json = gson.toJson(lAnime);
                        writer.write(json);
                        writer.close();
                        /**
                         if (DropboxManager.getInstance(context).isSyncLibraryEnabled())
                         {
                         String lAnimeFile = FileUtils.getValidFileName(lAnime.getTitle())
                         + "/"
                         + "manga.json";
                         DropboxManager.getInstance(context).storeFile(lAnimeFile, lFileName);
                         }
                         **/
                    } catch (IOException e) {
                        WriteLog.appendLog(Log.getStackTraceString(e));
                        e.printStackTrace();
                    }
                }
                return null;
            }

        };
        AsyncTaskUtils.executeAsyncTask(task, pAnime);
    }

    public static void viewAnime(Activity activity, String animeUrl) {
        viewAnime(activity, animeUrl, null, null);
    }

    public static void viewAnime(@NonNull Activity activity, @NonNull Anime anime, @Nullable View transitionView, @Nullable String transitionName) {
        viewAnime(activity, anime.getUrl(), transitionView, transitionName);
    }

    public static void viewAnime(Context context, AppCompatImageView imageView, Anime anime) {
        if (context == null) {
            WriteLog.appendLog("viewAnime called with null context");
            return;
        }
        if (anime == null) {
            WriteLog.appendLog("viewAnime called with null data");
            return;
        }
        String transitionName = context.getString(R.string.transition_anime_cover);
        Intent lIntent = new Intent();
        lIntent.setClass(context, AnimeDetailsActivity.class);
        lIntent.putExtra(Constants.ANIME_ID, anime.getId());
        lIntent.putExtra(Constants.ANIME_URL, anime.getUrl());
        //TransitionManager.setTransitionName(imageView, transitionName);
        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((AppCompatActivity) context, Pair.create((View) imageView, transitionName));
        ActivityCompat.startActivity(context, lIntent, options.toBundle());
        if (context instanceof AppCompatActivity) {
            ((AppCompatActivity) context).overridePendingTransition(0, 0);
        }
    }

    public static void viewAnime(@NonNull Activity activity, @NonNull String animeUrl, @Nullable View transitionView, @Nullable String transitionName) {
        Intent lIntent = new Intent(activity, AnimeDetailsActivity.class);
        lIntent.putExtra(Constants.ANIME_ID, animeUrl.hashCode());
        lIntent.putExtra(Constants.ANIME_URL, animeUrl);
        if (transitionView != null && !TextUtils.isEmpty(transitionName)) {
            ActivityOptionsCompat options =
                    ActivityOptionsCompat.makeSceneTransitionAnimation(activity,
                            transitionView,   // The view which starts the transition
                            transitionName    // The transitionName of the view we’re transitioning to
                    );
            ActivityCompat.startActivity(activity, lIntent, options.toBundle());
        } else
            ActivityCompat.startActivity(activity, lIntent, null);
    }
} public final class FileUtils {

    public static final String DEBUG_TAG = FileUtils.class.getSimpleName();

    private static final int BUFFER_SIZE = 8 * 1024; // 8 KB

    private FileUtils() {
    }

    //sorts based on the files name
    public static class SortByFileName implements Comparator<File> {
        AlphanumComparator mInteralComparator = new AlphanumComparator();

        @Override
        public int compare(File f1, File f2) {
            // Sort alphabetically by lower case, which is much cleaner
            return mInteralComparator.compare(f1.getName().toLowerCase(), f2.getName().toLowerCase());
        }
    }

    //sorts based on a file or folder. folders will be listed first
    public static class SortByFolder implements Comparator<File> {
        @Override
        public int compare(File f1, File f2) {
            if (f1.isDirectory() == f2.isDirectory())
                return 0;
            else if (f1.isDirectory() && !f2.isDirectory())
                return -1;
            else
                return 1;
        }
    }

    public static String getFileExtension(String fileName) {
        String[] splitExtension = fileName.split("\\.");
        if (splitExtension.length > 1) {
            String extension = splitExtension[splitExtension.length - 1];
            return extension.toLowerCase();
        } else {
            return "";
        }
    }

    public static String getFileName(String filePath) {
        String[] split = filePath.split("/");
        if (split.length > 1) {
            String fileName = split[split.length - 1];
            return fileName;
        } else {
            return "";
        }
    }

    public static String dirSize(String path) {

        File dir = new File(path);

        if (dir.exists()) {
            long bytes = getFolderSize(dir);
            if (bytes < 1024) return bytes + " B";
            int exp = (int) (Math.log(bytes) / Math.log(1024));
            String pre = ("KMGTPE").charAt(exp - 1) + "";

            return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
        }

        return "0";
    }

    public static long getFolderSize(File dir) {
        if (dir.exists()) {
            long result = 0;
            File[] fileList = dir.listFiles();
            for (int i = 0; i < fileList.length; i++) {
                // Recursive call if it's a directory
                if (fileList[i].isDirectory()) {
                    result += getFolderSize(fileList[i]);
                } else {
                    // Sum the file size in bytes
                    result += fileList[i].length();
                }
            }
            return result; // return the file size
        }
        return 0;
    }

    public static long getFolderCount(File dir) {
        if (dir.exists()) {
            long result = 0;
            File[] fileList = dir.listFiles();
            for (int i = 0; i < fileList.length; i++) {
                if (fileList[i].isDirectory())
                    result++;
            }
            return result; // return the file size
        }
        return 0;
    }

    public static boolean isReadable(String ext) {
        return "avi".equalsIgnoreCase(ext) || "mp4".equalsIgnoreCase(ext);
    }

    public static boolean isFont(String fileName) {
        String extension = getExtension(fileName);
        return "ttf".equalsIgnoreCase(extension);
    }

    public static boolean isImageUrl(String url) {
        String extension = URLConnection.guessContentTypeFromName(url);
        return "jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension) || "png".equalsIgnoreCase(extension) || "gif".equalsIgnoreCase(extension) || "bmp".equalsIgnoreCase(extension);
    }

    public static boolean isImage(String ext) {
        String extension = getExtension(ext);
        return "jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension) || "png".equalsIgnoreCase(extension) || "gif".equalsIgnoreCase(extension) || "bmp".equalsIgnoreCase(extension);
    }

    public static boolean isVideo(String ext) {
        return "mp4".equalsIgnoreCase(ext) || "avi".equalsIgnoreCase(ext) || "webm".equalsIgnoreCase(ext) || "mpeg".equalsIgnoreCase(ext);
    }

    public static String getExtension(String file) {
        return file.substring(file.lastIndexOf(".")+1);
    }

    public static String stripExtension(String extension, String filename) {
        extension = "." + extension;
        if (filename.endsWith(extension)) {
            return filename.substring(0, filename.length() - extension.length());
        }
        return filename;
    }

    public static boolean validateFileName(String fileName) {
        return fileName.matches("^[^.\\\\/:*?\"<>|]?[^\\\\/:*?\"<>|]*")
                && getValidFileName(fileName).length() > 0;
    }

    public static String getValidFileName(String fileName) {
        String newFileName = fileName.replace(":", "")
                .replace("?", "")
                .replace("\"", "")
                .replace("\\", "")
                .replace("<", "")
                .replace(">", "")
                .replace("\\|", "")
                .replace(":", "")
                .replace(";", "")
                .replace("*", "").replace("/", "-").replaceAll("\\s$", "").trim();
        return newFileName;
    }

    public static void copyStream(InputStream is, OutputStream os) throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        while (true) {
            int count = is.read(bytes, 0, BUFFER_SIZE);
            if (count == -1) {
                break;
            }
            os.write(bytes, 0, count);
        }
    }

    public static boolean moveFile(String srcPath, String dstPath) {
        File file = new File(srcPath);
        // Destination directory
        return file.renameTo(new File(dstPath));
    }

    public static void copyDirectory(File sourceLocation, File targetLocation, FilenameFilter pFilternameFilter, FileFilter pFileFilter,
                                     boolean pDeleteSrc)
            throws IOException {

        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }

            String[] children = sourceLocation.list(pFilternameFilter);
            for (int i = 0; i < sourceLocation.listFiles(pFileFilter).length; i++) {

                copyDirectory(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]), pFilternameFilter, pFileFilter, pDeleteSrc);
            }
            if (pDeleteSrc)
                deleteDirectory(sourceLocation);
        } else {

            InputStream in = new FileInputStream(sourceLocation);

            OutputStream out = new FileOutputStream(targetLocation);

            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }

    }

    /**
     * Overloaded with String path input
     *
     * @param dirPath
     * @return
     */
    public static boolean deleteDirectory(String dirPath) {
        // convert to file handler
        File path = new File(dirPath);
        return deleteDirectory(path);
    }

    /**
     * This function will recursively delete directories and files.
     *
     * @param path File or Directory to be deleted
     * @return true indicates success.
     */
    public static boolean deleteDirectory(File path) {

        // check if path exists
        if (path == null)
            return false;
        if (path.exists()) {

            // is directory
            if (path.isDirectory()) {

                // get list of file in directory
                File[] files = path.listFiles();
                if (files != null)
                    for (int i = 0; i < files.length; i++) {

                        // if it a directory make recursive call
                        // to deleteFile method
                        if (files[i].isDirectory()) {
                            deleteDirectory(files[i]);
                        } else {
                            // delete actual file
                            files[i].delete();
                        }
                    }
            }
        }
        // finally delete the path
        return (path.delete());
    }

    public static void deleteAsync(String... pPaths) {
        if (pPaths == null || pPaths.length == 0)
            return;
        AsyncTask<String, Void, Void> task = new AsyncTask<String, Void, Void>() {

            @Override
            protected Void doInBackground(String... params) {
                String lPath = null;
                for (int i = 0; i < params.length; i++) {
                    lPath = params[i];
                    if (TextUtils.isEmpty(lPath))
                        continue;
                    deleteDirectory(lPath);
                }
                return null;
            }

        };
        AsyncTaskUtils.executeAsyncTask(task, pPaths);
    }

    public static boolean checkMD5(String md5, File updateFile) {
        if (md5 == null || md5.equals("") || updateFile == null || !updateFile.exists() || updateFile.isDirectory()) {
            Log.e(DEBUG_TAG, "MD5 String NULL or UpdateFile NULL");
            return false;
        }

        String calculatedDigest = calculateMD5(updateFile);
        if (calculatedDigest == null) {
            Log.e(DEBUG_TAG, "calculatedDigest NULL");
            return false;
        }
        Log.i(DEBUG_TAG, "checkMD5 for " + updateFile.getName());
        Log.i(DEBUG_TAG, "Calculated digest: " + calculatedDigest);
        Log.i(DEBUG_TAG, "Provided digest: " + md5);

        return calculatedDigest.equalsIgnoreCase(md5);
    }

    public static String calculateMD5(File updateFile) {
        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            Log.e(DEBUG_TAG, "Exception while getting Digest", e);
            return null;
        }

        InputStream is;
        try {
            is = new FileInputStream(updateFile);
        } catch (FileNotFoundException e) {
            Log.e(DEBUG_TAG, "Exception while getting FileInputStream", e);
            return null;
        }

        byte[] buffer = new byte[8192];
        int read;
        try {
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
            byte[] md5sum = digest.digest();
            BigInteger bigInt = new BigInteger(1, md5sum);
            String output = bigInt.toString(16);
            // Fill to 32 chars
            output = String.format("%32s", output).replace(' ', '0');
            return output;
        } catch (IOException e) {
            WriteLog.appendLog("Unable to process file for MD5, removing file");
            WriteLog.appendLog(Log.getStackTraceString(e));
            updateFile.delete();
            return "";
            //throw new RuntimeException("Unable to process file for MD5", e);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                Log.e(DEBUG_TAG, "Exception on closing MD5 input stream", e);
            }
        }
    }

    public static String fileToMD5(String filePath) {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(filePath);
            byte[] buffer = new byte[1024];
            MessageDigest digest = MessageDigest.getInstance("MD5");
            int numRead = 0;
            while (numRead != -1) {
                numRead = inputStream.read(buffer);
                if (numRead > 0)
                    digest.update(buffer, 0, numRead);
            }
            byte[] md5Bytes = digest.digest();
            return convertHashToString(md5Bytes);
        } catch (Exception e) {
            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                }
            }
        }
    }

    private static String convertHashToString(byte[] md5Bytes) {
        String returnVal = "";
        for (int i = 0; i < md5Bytes.length; i++) {
            returnVal += Integer.toString((md5Bytes[i] & 0xff) + 0x100, 16).substring(1);
        }
        return returnVal;
    }

    public static String getFileSize(long size) {
        if (size <= 0)
            return "0";
        final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    public static File getDisplayImage(String pPath) {
        File dir = new File(pPath);
        if (!dir.isDirectory())
            return dir;
        File[] file = dir.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String fileName) {
                String ext = FileUtils.getFileExtension(fileName);
                return FileUtils.isImage(ext);
            }
        });
        if (file != null) {
            for (File f : file) {
                if (f.getName().contains("cover")) {
                    return f;
                }
            }
            if (file.length > 0)
                return file[0];
        }
        return null;
    }

    public static File findParentWithName(File file, String parentName) {
        if (file != null) {
            if (file.getParentFile() != null) {
                if (file.getParentFile().getName().equals(parentName)) {
                    return file;
                } else {
                    return findParentWithName(file.getParentFile(), parentName);
                }
            }
        }
        return null;
    }

    public static class AlphanumComparator implements Comparator
    {
        private final boolean isDigit(char ch)
        {
            return ch >= 48 && ch <= 57;
        }

        /** Length of string is passed in for improved efficiency (only need to calculate it once) **/
        private final String getChunk(String s, int slength, int marker)
        {
            StringBuilder chunk = new StringBuilder();
            char c = s.charAt(marker);
            chunk.append(c);
            marker++;
            if (isDigit(c))
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (!isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            } else
            {
                while (marker < slength)
                {
                    c = s.charAt(marker);
                    if (isDigit(c))
                        break;
                    chunk.append(c);
                    marker++;
                }
            }
            return chunk.toString();
        }

        @Override
        public int compare(Object o1, Object o2)
        {
            if (!(o1 instanceof String) || !(o2 instanceof String))
            {
                return 0;
            }
            String s1 = (String)o1;
            String s2 = (String)o2;

            int thisMarker = 0;
            int thatMarker = 0;
            int s1Length = s1.length();
            int s2Length = s2.length();

            while (thisMarker < s1Length && thatMarker < s2Length)
            {
                String thisChunk = getChunk(s1, s1Length, thisMarker);
                thisMarker += thisChunk.length();

                String thatChunk = getChunk(s2, s2Length, thatMarker);
                thatMarker += thatChunk.length();

                // If both chunks contain numeric characters, sort them numerically
                int result = 0;
                if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
                {
                    // Simple chunk comparison by length.
                    int thisChunkLength = thisChunk.length();
                    result = thisChunkLength - thatChunk.length();
                    // If equal, the first different number counts
                    if (result == 0)
                    {
                        for (int i = 0; i < thisChunkLength; i++)
                        {
                            result = thisChunk.charAt(i) - thatChunk.charAt(i);
                            if (result != 0)
                            {
                                return result;
                            }
                        }
                    }
                } else
                {
                    result = thisChunk.compareTo(thatChunk);
                }

                if (result != 0)
                    return result;
            }

            return s1Length - s2Length;
        }
    }
} 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 StorageUtils {

    public static String DEFAULT_LIBRARY_FOLDER = "My Anime Viewer/";

    private static File cacheDir = null;
    private static File dataDir = null;
    private static File backupDir = null;
    private static File searchDataFile = null;
    private static String lastPath = "";

    public static final String ERROR = "No Space";
    private static boolean storageAllowed = false;

    static {
        if (!BuildUtils.isMarshmallowOrLater())
            StorageUtils.storageAllowed = true;
    }

    /**
     * @return the cacheDir
     */
    public static File getCacheDir() {
        return cacheDir;
    }

    /**
     * @param pCacheDir The cache directory
     */
    public static void setCacheDir(File pCacheDir) {
        StorageUtils.cacheDir = pCacheDir;
    }

    public static File getExtSDCard(Context c) {
        File externalFolder = null;
        File[] test = ContextCompat.getExternalCacheDirs(c.getApplicationContext());
        if (test != null && test.length > 0) {
            externalFolder = test[test.length - 1];
            if (externalFolder != null) {
                if (externalFolder.getAbsolutePath().contains("cache"))
                    externalFolder = externalFolder.getParentFile();
            }
        }
        return externalFolder;
    }

    /**
     * @param pDirectory The data directory
     */
    public static void setDataDirectory(File pDirectory) {
        dataDir = pDirectory;
        if (dataDir != null) {
            if (!dataDir.exists())
                dataDir.mkdirs();
            File nomediaFile = new File(dataDir, ".nomedia");
            if (!nomediaFile.exists())
                try {
                    if (nomediaFile.createNewFile())
                        WriteLog.appendLog(nomediaFile.getAbsolutePath() + " file created.");
                } catch (IOException e) {
                    WriteLog.appendLog(nomediaFile.getAbsolutePath() + " failed to be created.");
                    WriteLog.appendLog(Log.getStackTraceString(e));
                }
            else {
                WriteLog.appendLog(nomediaFile.getAbsolutePath() + " file exists.");
            }
            backupDir = new File(
                    StorageUtils.getDataDirectory(),
                    "Backup/");
            if (!backupDir.exists())
                backupDir.mkdirs();
        }
    }

    public static File getDataDirectory() {
        return dataDir;
    }

    public static File getBackupDirectory() {
        return backupDir;
    }

    /**
     * @return the lastPath
     */
    public static String getLastPath() {
        return lastPath;
    }

    /**
     * @param mLastPath the lastPath to set
     */
    public static void setLastPath(String mLastPath) {
        StorageUtils.lastPath = mLastPath;
    }

    /**
     * @return the searchDataFile
     */
    public static File getSearchDataFile() {
        return searchDataFile;
    }

    /**
     * @param mSearchDataFile the searchDataFile to set
     */
    public static void setSearchDataFile(File mSearchDataFile) {
        StorageUtils.searchDataFile = mSearchDataFile;
    }

    public static long getAvailableStorage() {

        String storageDirectory = null;
        storageDirectory = Environment.getExternalStorageDirectory().toString();

        try {
            StatFs stat = new StatFs(storageDirectory);
            long avaliableSize = ((long) stat.getAvailableBlocks() * (long) stat.getBlockSize());
            return avaliableSize;
        } catch (RuntimeException ex) {
            return 0;
        }
    }

    public static void requestStoragePermissions(Context context) {
        PermissionListener dialogPermissionListener =
                DialogOnDeniedPermissionListener.Builder
                        .withContext(context)
                        .withTitle("Write to Storage Permission")
                        .withMessage("Storage permission is required to save any manga to your phone.")
                        .withButtonText(android.R.string.ok)
                        .withIcon(R.mipmap.ic_launcher)
                        .build();
        Dexter.withActivity((AppCompatActivity) context).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE).withListener(dialogPermissionListener).check();
    }

    public static void requestStoragePermissions(Context context, PermissionListener listener) {
        Dexter.withActivity((Activity) context).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE).withListener(listener).check();
    }

    public static void requestStoragePermissions(PermissionListener listener) {
        if (BuildUtils.isMarshmallowOrLater()) {
            //if (!Dexter.isRequestOngoing()) Dexter.checkPermission(listener, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        } else {
            storageAllowed = true;
        }
    }

    public static boolean isStorageAllowed() {
        return storageAllowed;
    }

    public static void setStorageAllowed(boolean storageAllowed) {
        StorageUtils.storageAllowed = storageAllowed;
    }

    public static boolean isSDCardPresent() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    public static boolean isSDCardWritable() {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return true;
        }
        return false;
    }
} 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 static int mConnectTimeOut = 60000; public static int mParseTimeOut = 120000;
import android.accounts.NetworkErrorException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.text.TextUtils;
import android.util.Log;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import com.koushikdutta.ion.ProgressCallback;
import com.taskdesignsinc.android.myanimeviewer.DownloadListActivity;
import com.taskdesignsinc.android.myanimeviewer.MAVApplication;
import com.taskdesignsinc.android.myanimeviewer.R;
import com.taskdesignsinc.android.myanimeviewer.model.Anime;
import com.taskdesignsinc.android.myanimeviewer.model.Episode;
import com.taskdesignsinc.android.myanimeviewer.parser.Parser;
import com.taskdesignsinc.android.myanimeviewer.service.download.error.FileAlreadyExistException;
import com.taskdesignsinc.android.myanimeviewer.service.download.error.NoMemoryException;
import com.taskdesignsinc.android.myanimeviewer.model.helper.AnimeUtils;
import com.taskdesignsinc.android.myanimeviewer.util.FileUtils;
import com.taskdesignsinc.android.myanimeviewer.util.NetworkUtils;
import com.taskdesignsinc.android.myanimeviewer.util.StorageUtils;
import com.taskdesignsinc.android.myanimeviewer.util.WriteLog;
import org.apache.http.conn.ConnectTimeoutException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import static com.taskdesignsinc.android.myanimeviewer.parser.Parser.mConnectTimeOut;
import static com.taskdesignsinc.android.myanimeviewer.parser.Parser.mParseTimeOut;
package com.taskdesignsinc.android.myanimeviewer.service.download;








public class AnimeDownloadTask extends AsyncTask<Void, Integer, Long> {

    public enum Order implements Comparator<AnimeDownloadTask> {
        ByIndex() {
            @Override
            public int compare(AnimeDownloadTask task1, AnimeDownloadTask task2) {

                int diff = (int) (task1.getId() - task2.getId());

                // ascending order
                return diff;
            }
        }
    }

    private int mId;

    public final static int TIME_OUT = 60000 * 60;
    private final static int BUFFER_SIZE = 1024 * 8;

    private static final String TAG = AnimeDownloadTask.class.getSimpleName();
    private static final boolean DEBUG = true;
    private static final String TEMP_SUFFIX = ".download";

    private File file;
    private File tempFile;
    private String videoUrl;
    private RandomAccessFile outputStream;
    private AnimeDownloadTaskListener listener;
    private Context context;

    private long downloadSize;
    private long previousFileSize;
    private long totalSize;
    private long downloadPercent;
    private long networkSpeed;
    private long previousTime;
    private long totalTime;
    private Throwable error = null;
    private boolean interrupt = false;

    private Anime anime;
    private Episode episode;
    private File downloadDir;

    private NotificationManager mNotifyManager;
    private Builder mBuilder;
    private File mAnimeCover;

    private String episodeUrl;

    private SharedPreferences mPrefs;
    private Parser mParser;

    public int getId() {
        return mId;
    }

    public void setId(int mId) {
        this.mId = mId;
    }

    private final class ProgressReportingRandomAccessFile extends RandomAccessFile {
        private int progress = 0;
        private int latestPercentDone;
        private long totalSize;
        private int percentDownloaded;

        public ProgressReportingRandomAccessFile(File file, String mode, long totalSize2)
                throws FileNotFoundException {
            super(file, mode);
            this.totalSize = totalSize2;
        }

        @Override
        public void write(byte[] buffer, int offset, int count) throws IOException {

            super.write(buffer, offset, count);
            progress += count;
            latestPercentDone = (int) ((progress / (float) this.totalSize) * 100.0);
            if (percentDownloaded != latestPercentDone) {
                percentDownloaded = latestPercentDone;
                publishProgress(progress);
            }
        }
    }

    public AnimeDownloadTask(int id, Context context, String pEpisodeURL) throws MalformedURLException {
        this(id, context, pEpisodeURL, null);
    }

    public AnimeDownloadTask(int id, Context context, String pEpisodeURL, AnimeDownloadTaskListener listener)
            throws MalformedURLException {
        super();
        this.mId = id;
        this.listener = listener;
        this.context = context;
        episodeUrl = pEpisodeURL;
        episode = MAVApplication.getInstance().getRepository().getEpisodeByUrl(pEpisodeURL);
        if (episode != null) {
            anime = MAVApplication.getInstance().getRepository().getAnimeByUrl(episode.getAnimeUrl(), true);
            if (anime != null) {
                downloadDir = new File(StorageUtils.getDataDirectory(),