package com.github.sgelb.booket.models;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.ImageView;

import com.github.sgelb.booket.R;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;

public class BitmapLoader {
    private static final String LOG_TAG = "BitmapLoader";
    private final Context context;
    private final BitmapCache cache;
    private final Bitmap placeholder;

    public BitmapLoader(Context context) {
        this.context = context;
        this.cache = BitmapCache.getInstance();
        this.placeholder = drawableToBitmap(ContextCompat.getDrawable(context, R.drawable.ic_blank_page));
    }

    public void load(Book book, ImageView imageView) {
        String bid = book.getGoogleId();

        // from cache
        Bitmap bitmap = cache.getBitmap(bid);
        if (bitmap != null) {
            Log.d(LOG_TAG, "Load bitmap from cache " + book.getTitle());
            imageView.setImageBitmap(bitmap);
            return;
        }

        final WeakReference<ImageView> imageViewReference = new WeakReference<>(imageView);

        // TODO: test for file and move actual loading to AsyncTask
        // from disk
        bitmap = loadFromDisk(bid);
        if (bitmap != null) {
            Log.d(LOG_TAG, "Load bitmap from disk " + book.getTitle());
            setCachedResizedBitmap(bitmap, bid, imageViewReference);
            return;
        }

        Log.d(LOG_TAG, "Load bitmap from web " + book.getTitle());
        download(book, imageView);
    }

    private Bitmap loadFromDisk(String key) {
        String filename = key + ".jpg";
        FileInputStream fis;
        Bitmap bitmap = null;
        try {
            fis = context.openFileInput(filename);
            bitmap = BitmapFactory.decodeStream(fis);
            fis.close();
        } catch (FileNotFoundException e) {
            return null;
        } catch (IOException e) {
            // could not close fis
            e.printStackTrace();
        }
        return bitmap;
    }

    private void setCachedResizedBitmap(Bitmap bitmap, String key, WeakReference<ImageView> imageViewWeakReference) {
        bitmap = resizeBitmapToWidth(bitmap);
        // always cache bitmaps loaded from disk or net
        cache.addBitmap(key, bitmap);
        if (imageViewWeakReference != null) {
            // only shown if referencing imageView still exists
            imageViewWeakReference.get().setImageBitmap(bitmap);
        }
    }

    private Bitmap resizeBitmapToWidth(Bitmap bitmap) {
        int width = (int) context.getResources().getDimension(R.dimen.thumbnail_width);
        int height = width * bitmap.getHeight() / bitmap.getWidth();
        return Bitmap.createScaledBitmap(bitmap, width, height, true);
    }

    private void download(Book book, ImageView imageView) {
        if (cancelPotentialDownload(book.getGoogleId(), imageView)) {
            Log.d(LOG_TAG, "Downloading cover for " + book.getTitle());
            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
            DownloadedDrawable downloadedDrawable = new DownloadedDrawable(context.getResources(),
                    placeholder, task);
            imageView.setImageDrawable(downloadedDrawable);
            task.execute(book.getGoogleId());
        }
    }

    private static boolean cancelPotentialDownload(String bid, ImageView imageView) {
        BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

        if (bitmapDownloaderTask != null) {
            String bitmapId = bitmapDownloaderTask.bid;
            if ((bitmapId == null) || (!bitmapId.equals(bid))) {
                bitmapDownloaderTask.cancel(true);
            } else {
                // URL is already being downloaded
                return false;
            }
        }
        // there was no download on this imageView or current download has been canceled
        return true;
    }

    private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
        if (imageView != null) {
            Drawable drawable = imageView.getDrawable();
            if (drawable instanceof DownloadedDrawable) {
                DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
                return downloadedDrawable.getBitmapDownloaderTask();
            }
        }
        return null;
    }

    private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
        private String bid;
        private final WeakReference<ImageView> imageViewReference;

        BitmapDownloaderTask(ImageView imageView) {
            imageViewReference = new WeakReference<>(imageView);
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            bid = params[0];
            return downloadBitmap(bid);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
                bitmap = null;
            }

            bitmap = resizeBitmapToWidth(bitmap);
            cache.addBitmap(bid, bitmap);

            ImageView imageView = imageViewReference.get();
            BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
            // Change bitmap only if this process is still associated with it
            if (this == bitmapDownloaderTask) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }

    private Bitmap downloadBitmap(String googleId) {
        Bitmap bitmap = null;
        HttpURLConnection con = null;
        //String isbn = BookHelper.isbn13to10(params[0]);

        URL url;
        // TODO: separate download sources
        String[] thumbnailUrls = {
                //"https://images-na.ssl-images-amazon.com/images/P/" + isbn + ".01",
                "https://books.google.de/books?id=" + googleId + "&printsec=frontcover&img=1&zoom=1"
        };

        for (String thumbnailUrl : thumbnailUrls) {
            try {
                url = new URL(thumbnailUrl);
                con = (HttpURLConnection) url.openConnection();
                con.setConnectTimeout(10000);
                con.setDoInput(true);
                con.connect();
                if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    InputStream input = con.getInputStream();
                    bitmap = BitmapFactory.decodeStream(input);
                }
            } catch (MalformedURLException e) {
                Log.d("BOOKET", "MalformedURLException: " + e.getMessage());
            } catch (SocketTimeoutException e) {
                Log.d("BOOKET", "SocketTimeoutException: " + e.getMessage());
            } catch (IOException e) {
                Log.d("BOOKET", "IOException: " + e.getMessage());
            } finally {
                if (con != null) {
                    con.disconnect();
                }
            }
            if (bitmap != null && bitmap.getWidth() > 1) {
                return bitmap;
            }
        }
        return bitmap;
    }

    /**
     * A fake Drawable that will be attached to the imageView while the download is in progress.
     *
     * <p>Contains a reference to the actual download task, so that a download task can be stopped
     * if a new binding is required, and makes sure that only the last started download process can
     * bind its result, independently of the download finish order.</p>
     */
    private static class DownloadedDrawable extends BitmapDrawable {
        private final WeakReference<BitmapDownloaderTask> taskReference;

        DownloadedDrawable(Resources res, Bitmap bitmap, BitmapDownloaderTask task) {
            super(res, bitmap);
            taskReference = new WeakReference<>(task);
        }

        BitmapDownloaderTask getBitmapDownloaderTask() {
            return taskReference.get();
        }
    }

    private Bitmap drawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return resizeBitmapToWidth(bitmap);
    }
}
