public abstract class Parser {
    private static final String TAG = Parser.class.getSimpleName();
    private static final int PARSER_ID = -1;
    String URL_BASE;
    private static  OkHttpClient client;
    private static InjectedCookieJar injectedCookieJar;

    private static Parser instance;
    public static Parser getInstance(){
        return instance;
    }

    public static void selectParser(Context context, int id){
        instance = selectByParserId(context, id);
        initHttpClient(context);
    }
    public static void selectParser(Context context, int id, String url){
        instance = selectByParserId(id, url);
        initHttpClient(context);
    }
    public static Parser selectByParserId(Context context, int id){
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        String url = preferences.getString("url", context.getString(R.string.defaul_url));
        return selectByParserId(id, url);
    }
    private static Parser selectByParserId(int id, String url) {
        if (!url.endsWith("/")) url = url + "/";
        Log.i(TAG, "selectByParserId: load with #" + id + " for " + url);
        switch (id) {
            case KinoxParser.PARSER_ID:
                return new KinoxParser(url);
            case Movie4kParser.PARSER_ID:
                return new Movie4kParser(url);
        }
        return null;
    }

    private static void initHttpClient(Context context){
        injectedCookieJar = new InjectedCookieJar();
        client = new OkHttpClient.Builder()
                .followRedirects(false)
                .followSslRedirects(false)
                .addNetworkInterceptor(new UserAgentInterceptor(Utils.USER_AGENT))
                .addInterceptor(new CloudflareDdosInterceptor(context))
                .cookieJar(injectedCookieJar)
                .dns(new CustomDns())
                .build();
    }

    Document getDocument(String url) throws IOException {
        return getDocument(url, null);
    }

    Document getDocument(String url, Map<String, String> cookies) throws  IOException {
        Request request = new Request.Builder()
                .url(url)
                .build();

        if(cookies != null) {
            for (String key : cookies.keySet()) {
                injectedCookieJar.addCookie(new Cookie.Builder()
                        .domain(request.url().host())
                        .name(key)
                        .value(cookies.get(key))
                        .build()
                );
            }
        }
        Response response = client.newCall(request).execute();
        if(response.code() != 200){
            throw new IOException("Unexpected status code " + response.code());
        }
        String body = response.body().string();
        if(TextUtils.isEmpty(body)){
            throw new IOException("Body for " + url + " is empty");
        }
        return Jsoup.parse(body);
    }

    public String getBody(String url) {
        OkHttpClient noFollowClient = client.newBuilder().followRedirects(false).build();
        Request request = new Request.Builder().url(url).build();
        Log.i(TAG, "read text from " + url + ", cookies=" + noFollowClient.cookieJar().toString());
        try {
            Response response = noFollowClient.newCall(request).execute();
            Log.i(TAG, "Got " + response.code() + " for " + url + ", cookies=" + noFollowClient.cookieJar().toString());
            for(String key : response.headers().names()){
                Log.i(TAG, key + "=" + response.header(key));
            }

            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    JSONObject getJson(String url) {
        Request request = new Request.Builder().url(url).build();

        Log.i("Utils", "read json from " + url);
        try {
            Response response = client.newCall(request).execute();
            return new JSONObject(response.body().string());
        } catch (IOException | JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Parser(String url) {
        this.URL_BASE = url;
    }

    public abstract String getParserName();

    public int getParserId(){
        return PARSER_ID;
    }

    public abstract List<ViewModel> parseList(String url) throws IOException;

    public abstract ViewModel loadDetail(ViewModel item);

    public abstract ViewModel loadDetail(String url);

    public abstract List<Host> getHosterList(ViewModel item, int season, String episode);

    public abstract String getMirrorLink(DetailActivity.QueryPlayTask queryTask, ViewModel item, int hoster, int mirror);

    public abstract String getMirrorLink(DetailActivity.QueryPlayTask queryTask, ViewModel item, int hoster, int mirror, int season, String episode);

    public abstract String[] getSearchSuggestions(String query);

    public abstract String getPageLink(ViewModel item);

    public abstract String getSearchPage(String query);

    public abstract String getCineMovies();

    public abstract String getPopularMovies();

    public abstract String getLatestMovies();

    public abstract String getPopularSeries();

    public abstract String getLatestSeries();

    public String getUrl(){
        return URL_BASE;
    }

    @Override
    public String toString() {
        return getParserName();
    }

    public OkHttpClient getClient(){
        return client;
    }

} public abstract class Host {
    protected int mirror;
    protected String url;

    public static Class<?>[] HOSTER_LIST = {
            DivxStage.class,
            NowVideo.class,
            SharedSx.class,
            Sockshare.class,
            StreamCloud.class,
            Vodlocker.class,
    };

    public static Host selectById(int id){
        for (Class<?> h: HOSTER_LIST) {
            try {
                Host host = (Host) h.getConstructor().newInstance();
                if(host.getId() == id){
                    return host;
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public Host(){

    }

    public Host(int mirror){
        this.mirror = mirror;
    }

    public boolean isEnabled(){
        return false;
    }

    public abstract int getId();
    public abstract String getName();

    public int getMirror() {
        return mirror;
    }

    public void setMirror(int mirror) {
        this.mirror = mirror;
    }

    public String getUrl() {
        return url;
    }

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

    public String getVideoPath(DetailActivity.QueryPlayTask queryTask){
        return null;
    }

    @Override
    public String toString() {
        return getName() + " #" + mirror;
    }
} public class ViewModel implements Serializable {

    private int seriesID;
    private String slug;
    private String title;
    private String image;
    private String summary;
    private float rating;
    private int languageResId;
    private String genre;
    private String imdbId;
    @SuppressWarnings("unused")
    private String year;

    private Type type;

    private transient Season[] seasons;
    private transient Host[] mirrors;

    public ViewModel() {

    }

    public int getSeriesID() {
        return seriesID;
    }

    public void setSeriesID(int seriesID) {
        this.seriesID = seriesID;
    }

    public String getSlug() {
        return slug;
    }

    public void setSlug(String slug) {
        this.slug = slug;
    }

    public String getTitle() {
        return title;
    }

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

    public void setImage(String image) {
        this.image = image;
    }

    public String getImage() {
        return this.image;
    }

    public String getSummary() {
        return summary;
    }

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

    public float getRating() {
        return rating;
    }

    public void setRating(float rating) {
        this.rating = rating;
    }

    public int getLanguageResId() {
        return languageResId;
    }

    public void setLanguageResId(int languageResId) {
        this.languageResId = languageResId;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }

    public Season[] getSeasons() {
        return seasons;
    }

    public void setSeasons(Season[] seasons) {
        this.seasons = seasons;
    }

    public Host[] getMirrors() {
        return mirrors;
    }

    public void setMirrors(Host[] mirrors) {
        this.mirrors = mirrors;
    }

    public Type getType() {
        return type;
    }

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

    public String getImdbId() {
        return imdbId;
    }

    public void setImdbId(String imdbId) {
        this.imdbId = imdbId;
    }

    public enum Type{
        MOVIE,
        SERIES,
        DOCUMENTATION
    }
} public class PaletteManager {
    private LruCache<String, Palette> cache = new LruCache<>(100);
    private static PaletteManager instance;

    public static PaletteManager getInstance(){
        if(instance == null) instance = new PaletteManager();
        return instance;
    }

    public interface Callback {
        void onPaletteReady(Palette palette);
    }

    public void getPalette(final String key, Bitmap bitmap, final Callback callback) {
        Palette palette = cache.get(key);
        if (palette != null)
            callback.onPaletteReady(palette);
        else
            if(bitmap == null){
                callback.onPaletteReady(null);
            } else {
                Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
                  public void onGenerated(Palette p) {
                      cache.put(key, p);
                      callback.onPaletteReady(p);
                  }
                });
            }
    }
} public class OkHttpViewModelStreamFetcher implements DataFetcher<InputStream> {
    private TheMovieDb tmdbCache;

    private final Call.Factory client;
    private final ViewModelGlideRequest model;
    private InputStream stream;
    private ResponseBody responseBody;
    private volatile Call call;

    OkHttpViewModelStreamFetcher(Call.Factory client, ViewModelGlideRequest model, TheMovieDb tmdbCache) {
        this.client = client;
        this.model = model;
        this.tmdbCache = tmdbCache;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        GlideUrl url;
        //String cacheUrl = Parser.getInstance().getPageLink(model.getViewModel()) + "?size=" + model.getScreenWidthPx() + "&type=" + model.getType();
        String cacheUrl = Parser.getInstance().getPageLink(model.getViewModel());

        String imageUrl = null;
        try {
            //Map<String, List<String>> para = Utils.splitHashQuery(new URL(cacheUrl));
            JSONObject json = tmdbCache.get(cacheUrl);
            if(json != null){
                String type = model.getType();
                String key = type + "_path";
                int size = model.getScreenWidthPx();
                if(type.equals("backdrop"))
                    imageUrl = TheMovieDb.IMAGE_BASE_PATH + getBackdropSize(size) + json.getString(key);
                else
                    imageUrl = TheMovieDb.IMAGE_BASE_PATH + getPosterSize(size) + json.getString(key);

            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        url = new GlideUrl(imageUrl);

        Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());

        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey();
            requestBuilder.addHeader(key, headerEntry.getValue());
        }
        Request request = requestBuilder.build();

        Response response;
        call = client.newCall(request);
        response = call.execute();
        responseBody = response.body();
        if (!response.isSuccessful()) {
            throw new IOException("Request failed with code: " + response.code());
        }

        long contentLength = responseBody.contentLength();
        stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
        return stream;
    }

    @Override
    public void cleanup() {
        try {
            if (stream != null) {
                stream.close();
            }
        } catch (IOException e) {
            // Ignored
        }
        if (responseBody != null) {
            responseBody.close();
        }
    }

    @Override
    public String getId() {
        return model.getViewModel().getSlug();
    }

    @Override
    public void cancel() {
        Call local = call;
        if (local != null) {
            local.cancel();
        }
    }


    public static String getBackdropSize(int width){
        String size = "w300";
        if(width > 300) size = "w780";
        if(width > 780) size = "w1280";
        if(width > 1280) size = "original";
        return size;
    }

    public static String getPosterSize(int width){
        String size = "w92";
        if(width > 92) size = "w154";
        if(width > 154) size = "w185";
        if(width > 185) size = "w342";
        if(width > 342) size = "w500";
        if(width > 500) size = "w780";
        if(width > 780) size = "original";
        return size;
    }
} public class ViewModelGlideRequest {
    private ViewModel viewModel;
    private int screenWidthPx;
    private String type;

    public ViewModelGlideRequest(ViewModel viewModel, int screenWidthPx, String type) {
        this.viewModel = viewModel;
        this.screenWidthPx = screenWidthPx;
        this.type = type;
    }

    ViewModel getViewModel() {
        return viewModel;
    }

    int getScreenWidthPx() {
        return screenWidthPx;
    }

    public String getType() {
        return type;
    }
} public class BookmarkManager extends ArrayList<BookmarkManager.Bookmark> {
    private static final String FILENAME = "bookmark.dat";

    private transient Context context;
    private transient boolean autoSave = true;

    public BookmarkManager(Context context){
        this.context = context;
        restore();
    }

    private void save(){
        try {
            File f = new File(context.getFilesDir(), FILENAME);
            if(f.exists()) f.delete();
            FileOutputStream fos = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
            ObjectOutputStream os = new ObjectOutputStream(fos);
            os.writeObject(this);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void restore(){
        try {
            FileInputStream fis = context.openFileInput(FILENAME);
            ObjectInputStream is = new ObjectInputStream(fis);
            BookmarkManager simpleClass = (BookmarkManager) is.readObject();
            is.close();
            simpleClass.setAutoSave(false);
            Collections.sort(simpleClass, new Comparator<Bookmark>() {
                @Override
                public int compare(Bookmark s1, Bookmark s2) {
                    return s1.getUrl().compareToIgnoreCase(s2.getUrl());
                }
            });
            clear();
            for(int i = 0; i < simpleClass.size(); i++){
                super.add(simpleClass.get(i));
            }
        } catch (Exception ignored) {

        }
    }

    @Override
    public boolean add(Bookmark object) {
        boolean r = super.add(object);
        if(isAutoSave()) save();
        return r;
    }

    @Override
    public boolean remove(Object object) {
        boolean r = super.remove(object);
        if(isAutoSave()) save();
        return r;
    }

    @Override
    public Bookmark set(int index, Bookmark object) {
        Bookmark r = super.set(index, object);
        if(isAutoSave()) save();
        return r;
    }

    public void addAsPublic(Bookmark bookmark) {
        bookmark.setInternal(false);
        int idx = indexOf(bookmark);
        if(idx == -1){
            add(bookmark);
        } else {
            set(idx, bookmark);
        }
    }

    public Bookmark findItem(ViewModel item){
        Bookmark b = new BookmarkManager.Bookmark(Parser.getInstance().getParserId(), Parser.getInstance().getPageLink(item));
        int idx = indexOf(b);
        if(idx == -1){
            return null;
        } else {
            return get(idx);
        }
    }

    private boolean isAutoSave() {
        return autoSave;
    }

    private void setAutoSave(boolean autoSave) {
        this.autoSave = autoSave;
    }

    public static class Bookmark implements Serializable{
        private int parserId;
        private String url;
        private int season = 0;
        private int episode = 0;
        private boolean internal = true;

        public Bookmark(int parserId, String url) {
            this.parserId = parserId;
            this.url = url;
        }

        public int getParserId() {
            return parserId;
        }

        public String getUrl() {
            return url;
        }

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

        public int getEpisode() {
            return episode;
        }

        public void setEpisode(int episode) {
            this.episode = episode;
        }

        public int getSeason() {
            return season;
        }

        public void setSeason(int season) {
            this.season = season;
        }

        public boolean isInternal() {
            return internal;
        }

        public void setInternal(boolean internal) {
            this.internal = internal;
        }

        @Override
        public boolean equals(Object o) {
            if(o instanceof Bookmark){
                Bookmark b = (Bookmark)o;
                return b.getParserId() == this.getParserId() && TextUtils.equals(b.getUrl(), this.getUrl());
            }
            return super.equals(o);
        }
    }
} public class TheMovieDb {
    public static final String IMAGE_BASE_PATH = "http://image.tmdb.org/t/p/";
    private static final String DISK_CACHE_PATH = "/themoviedb_cache/";
    private static final String API_KEY = "f9dc7e5d12b2640bf4ef1cf20835a1cc";

    private ConcurrentHashMap<String, SoftReference<JSONObject>> memoryCache;
    private String diskCachePath;
    private boolean diskCacheEnabled = false;
    private ExecutorService writeThread;


    public TheMovieDb(Context context) {
        // Set up in-memory cache store
        memoryCache = new ConcurrentHashMap<>();

        // Set up disk cache store
        Context appContext = context.getApplicationContext();
        diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;

        File outFile = new File(diskCachePath);
        outFile.mkdirs();

        diskCacheEnabled = outFile.exists();

        // Set up threadpool for image fetching tasks
        writeThread = Executors.newSingleThreadExecutor();
    }

    public JSONObject get(String url) {
        return get(url, true);
    }

    public JSONObject get(String url, boolean fetchIfNeeded) {
        JSONObject json;
        //String url = request.getUrl();

        // Check for image in memory
        json = getFromMemory(url);

        // Check for image on disk cache
        if(json == null) {
            json = getFromDisk(url);

            // Write bitmap back into memory cache
            if(json != null) {
                cacheToMemory(url, json);
            }
        }
        if(json == null || fetchIfNeeded) {
            try {
                // Get IMDB-ID from page
                ViewModel item = Parser.getInstance().loadDetail(url);
                String param = url.substring(url.indexOf("#") + 1);
                // tt1646971?api_key=f9dc7e5d12b2640bf4ef1cf20835a1cc&language=de&external_source=imdb_id
                JSONObject data = Utils.readJson("http://api.themoviedb.org/3/find/" + item.getImdbId() + "?api_key=" + API_KEY + "&external_source=imdb_id&" + param);
                if(data == null) return null;
                if(data.getJSONArray("movie_results").length() > 0) {
                    json = data.getJSONArray("movie_results").getJSONObject(0);
                }else if(data.getJSONArray("tv_results").length() > 0) {
                    json = data.getJSONArray("tv_results").getJSONObject(0);
                }
                if(json != null) {
                    put(url, json);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        return json;
    }

    private void put(String url, JSONObject json) {
        cacheToMemory(url, json);
        cacheToDisk(url, json);
    }

    private JSONObject getFromMemory(String url) {
        JSONObject json = null;
        SoftReference<JSONObject> softRef = memoryCache.get(getCacheKey(url));
        if(softRef != null){
            json = softRef.get();
        }

        return json;
    }

    private JSONObject getFromDisk(String url) {
        JSONObject json = null;
        if(diskCacheEnabled){
            String filePath = getFilePath(url);
            File file = new File(filePath);
            if(file.exists()) {
                try {
                    BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
                    StringBuilder content = new StringBuilder();
                    char[] buffer = new char[1024];
                    int num;
                    while ((num = input.read(buffer)) > 0) {
                        content.append(buffer, 0, num);
                    }
                    json = new JSONObject(content.toString());
                } catch (JSONException | IOException e) {
                    e.printStackTrace();
                }

            }
        }
        return json;
    }

    private void cacheToMemory(final String url, final JSONObject json) {
        memoryCache.put(getCacheKey(url), new SoftReference<>(json));
    }

    private void cacheToDisk(final String url, final JSONObject json) {
        writeThread.execute(new Runnable() {
            @Override
            public void run() {
                if(diskCacheEnabled) {
                    FileOutputStream ostream = null;
                    try {
                        ostream = new FileOutputStream(new File(diskCachePath, getCacheKey(url)));
                        ostream.write(json.toString().getBytes());
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if(ostream != null) {
                                ostream.flush();
                                ostream.close();
                            }
                        } catch (IOException ignored) {}
                    }
                }
            }
        });
    }

    private String getFilePath(String url) {
        return diskCachePath + getCacheKey(url);
    }

    private String getCacheKey(String url) {
        if(url == null){
            throw new RuntimeException("Null url passed in");
        } else {
            if(url.contains("#"))
                url = url.substring(0, url.indexOf("#"));
            return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");
        }
    }
} public class Utils {
    public static final String USER_AGENT = "KinoCast v" + BuildConfig.VERSION_NAME;

    public static String getRedirectTarget(String url){
        OkHttpClient client = new OkHttpClient.Builder()
                .followRedirects(false)
                .addNetworkInterceptor(new UserAgentInterceptor(USER_AGENT))
                .build();
        Request request = new Request.Builder().url(url).build();
        try {
            Response response = client.newCall(request).execute();
            return response.header("Location");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static JSONObject readJson(String url) {
        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new UserAgentInterceptor(USER_AGENT))
                .build();
        Request request = new Request.Builder().url(url).build();

        Log.i("Utils", "read json from " + url);
        try {
            Response response = client.newCall(request).execute();
            return new JSONObject(response.body().string());
        } catch (IOException | JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings("deprecation")
    public static boolean isWifiConnected(Context context) {
        ConnectivityManager connManager = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        return ((netInfo != null) && netInfo.isConnected());
    }

    public static SparseIntArray getWeightedHostList(Context context){
        SparseIntArray sparseArray = new SparseIntArray();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        int count = preferences.getInt("order_hostlist_count", -1);
        if(count == -1) return null;
        for(int i = 0; i < count; i++){
            int key = preferences.getInt("order_hostlist_" + i, i);
            sparseArray.put(key, i);
        }
        return sparseArray;
    }

    public static VideoCastManager initializeCastManager(Context context) {
        CastConfiguration.Builder builder = new CastConfiguration.Builder(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
                .enableAutoReconnect()
                .enableCaptionManagement()
                .enableWifiReconnection();

        if(BuildConfig.DEBUG){
            builder.enableDebug();
        }

        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        if(preferences.getBoolean("chromecast_lock_screen", true)){
            builder.enableLockScreen();
        }

        if(preferences.getBoolean("chromecast_notification", true)){
            builder.enableNotification()
                    .addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE, true)
                    .addNotificationAction(CastConfiguration.NOTIFICATION_ACTION_DISCONNECT, true);
        }

        return VideoCastManager.initialize(context, builder.build());
    }
} public class WeightedHostComparator implements Comparator<Host> {
    private SparseIntArray weightedList;

    public WeightedHostComparator(SparseIntArray weightedList){
        this.weightedList = weightedList;
    }

    @Override
    public int compare(Host o1, Host o2) {
        if(weightedList == null) return compate(o1.getMirror(), o2.getMirror());
        int w1 = weightedList.get(o1.getId(), o1.getId());
        int w2 = weightedList.get(o2.getId(), o2.getId());
        if(w1 == w2){ // Same host, sort by mirror
            compate(o1.getMirror(), o2.getMirror());
        }
        return compate(w1, w2);
    }

    private int compate(int x, int y){
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.flurry.android.FlurryAgent;
import com.flurry.android.ads.FlurryAdBanner;
import com.flurry.android.ads.FlurryAdBannerListener;
import com.flurry.android.ads.FlurryAdErrorType;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.common.images.WebImage;
import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
import com.ov3rk1ll.kinocast.BuildConfig;
import com.ov3rk1ll.kinocast.R;
import com.ov3rk1ll.kinocast.api.Parser;
import com.ov3rk1ll.kinocast.api.mirror.Host;
import com.ov3rk1ll.kinocast.data.Season;
import com.ov3rk1ll.kinocast.data.ViewModel;
import com.ov3rk1ll.kinocast.ui.helper.PaletteManager;
import com.ov3rk1ll.kinocast.ui.util.glide.OkHttpViewModelStreamFetcher;
import com.ov3rk1ll.kinocast.ui.util.glide.ViewModelGlideRequest;
import com.ov3rk1ll.kinocast.utils.BookmarkManager;
import com.ov3rk1ll.kinocast.utils.TheMovieDb;
import com.ov3rk1ll.kinocast.utils.Utils;
import com.ov3rk1ll.kinocast.utils.WeightedHostComparator;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

                PackageManager pm = getPackageManager();
                List<ResolveInfo> launchables = pm.queryIntentActivities(intent, 0);
                List<AppAdapter.App> apps = new ArrayList<>();
                Collections.sort(launchables, new ResolveInfo.DisplayNameComparator(pm));
                if (mVideoCastManager.isConnected()) {
                    apps.add(new AppAdapter.App(
                            getString(R.string.player_chromecast_list_entry),
                            getResources().getDrawable(R.drawable.ic_player_chromecast),
                            null
                    ));
                }
                apps.add(new AppAdapter.App(
                        getString(R.string.player_internal_list_entry),
                        getResources().getDrawable(R.drawable.ic_player),
                        new ComponentName(DetailActivity.this, PlayerActivity.class)
                ));
                for (ResolveInfo resolveInfo : launchables) {
                    ActivityInfo activity = resolveInfo.activityInfo;
                    AppAdapter.App app = new AppAdapter.App(
                            resolveInfo.loadLabel(pm),
                            resolveInfo.loadIcon(pm),
                            new ComponentName(activity.applicationInfo.packageName, activity.name)
                    );
                    apps.add(app);
                }

                final AppAdapter adapter = new AppAdapter(DetailActivity.this, apps, pm);
                builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int position) {
                        AppAdapter.App app = adapter.getItem(position);
                        Map<String, String> articleParams = new HashMap<>();
                        articleParams.put("Name", item.getTitle());
                        articleParams.put("Type", item.getType() == ViewModel.Type.MOVIE ? "Movie" : "Series");
                        articleParams.put("Id", item.getSlug());

                        articleParams.put("Hoster", host.getName());
                        articleParams.put("Movie", item.getTitle());
                        if (app.getComponent() == null) {
                            startPlaybackOnChromecast(link);
                            articleParams.put("Player", "Chromecast");
                        } else {
                            intent.setComponent(app.getComponent());
                            articleParams.put("Player", app.getComponent().toString());
                            startActivity(intent);
                        }
                        FlurryAgent.logEvent("Played_Stream", articleParams);
                        dialog.dismiss();
                    }
                });
                final AlertDialog dialog = builder.create();
                //dialog.getListView().setDivider(getResources().getDrawable(R.drawable.abc_list_divider_holo_light));
                dialog.getListView().setDividerHeight(1);
                dialog.show();

            } else { // no link found
                Toast.makeText(DetailActivity.this, getString(R.string.host_resolve_error), Toast.LENGTH_SHORT).show();
            }
        }

        public Context getContext() {
            return context;
        }
    }

    public void startPlaybackOnChromecast(String link) {
        MediaMetadata mediaMetadata;
        if (item.getType() == ViewModel.Type.SERIES) {
            Season s = item.getSeasons()[((Spinner) findViewById(R.id.spinnerSeason)).getSelectedItemPosition()];
            String e = s.episodes[((Spinner) findViewById(R.id.spinnerEpisode)).getSelectedItemPosition()];
            mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_TV_SHOW);
            mediaMetadata.putString(MediaMetadata.KEY_TITLE, item.getTitle() + " - Folge " + s.id + "x" + e);
        } else {
            mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
            mediaMetadata.putString(MediaMetadata.KEY_TITLE, item.getTitle());
        }
        mediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, getString(R.string.chromecast_subtitle));



        // Use TheMovieDb to get the image
        String url = getCachedImage(96, "poster");
        Log.i("Chromecast", "use image: " + url);
        if(TextUtils.isEmpty(url)) url = "http://kinocast.ov3rk1ll.com/img/kinocast_icon_512.png";
        mediaMetadata.addImage(new WebImage(Uri.parse(url)));

        // TODO Use Glide to get image
        url = getCachedImage(getResources().getDisplayMetrics().widthPixels, "poster"); // new CoverImage(item.getImageRequest(getResources().getDisplayMetrics().widthPixels, "poster")).getBitmapUrl(getApplication());
        Log.i("Chromecast", "use image: " + url);
        if(TextUtils.isEmpty(url)) url = "http://kinocast.ov3rk1ll.com/img/kinocast_icon_512.png";
        mediaMetadata.addImage(new WebImage(Uri.parse(url)));
        Log.i("cast", "play " + link);
        MediaInfo mediaInfo = new MediaInfo.Builder(link)
                .setContentType("video/mp4")
                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                .setMetadata(mediaMetadata)
                .build();

        mVideoCastManager.startVideoCastControllerActivity(DetailActivity.this, mediaInfo, 0, true);
    }

    /*public void startCastControllerActivity(Context context, Bundle mediaWrapper, int position, boolean shouldStart) {
        Intent intent = new Intent(context, ColorfulVideoCastControllerActivity.class);
        intent.putExtra(VideoCastManager.EXTRA_MEDIA, mediaWrapper);
        intent.putExtra(VideoCastManager.EXTRA_START_POINT, position);
        intent.putExtra(VideoCastManager.EXTRA_SHOULD_START, shouldStart);
        intent.putExtra(ColorfulVideoCastControllerActivity.EXTRA_AB_COLOR, mActionBarBackgroundDrawable.getColor());
        intent.putExtra(ColorfulVideoCastControllerActivity.EXTRA_TEXT_COLOR, mTitleColor);
        context.startActivity(intent);
    }*/

    private String getCachedImage(int size, String type){
        TheMovieDb tmdbCache = new TheMovieDb(getApplication());
        String cacheUrl = Parser.getInstance().getPageLink(item);
        JSONObject json = tmdbCache.get(cacheUrl, false);
        if(json != null){
            try {
                String key = type + "_path";
                if (type.equals("backdrop"))
                    return TheMovieDb.IMAGE_BASE_PATH + OkHttpViewModelStreamFetcher.getBackdropSize(size) + json.getString(key);