package com.nourayn.quran.Downloader;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.ProgressBar;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.widget.Toast;

import com.nourayn.quran.Database.AppPreference;
import com.fekracomputers.quran.R;
import com.nourayn.quran.UI.Activities.MainActivity;
import com.nourayn.quran.Utilities.AppConstants;
import com.nourayn.quran.Utilities.UnZipping;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * A Class Responsible For download management
 * Async task class can run in background as a service or normal thread
 */
public class DownloadManager extends AsyncTask<String, Long, Boolean> {
    public static final int DOWNLOAD_CHUNK_SIZE = 1024 * 3;
    private ProgressBar downloadProgressBar;
    private List<String> downloadLinks;
    private Context context;
    private TextView downloadInfo;
    private RemoteViews remoteViews;
    private NotificationManager notificationManager;
    private NotificationCompat.Builder builder;
    private boolean notificationDownloaderFlag;
    private int notificationDivider;
    private String fileExtension, fileName, filePath;
    private PendingIntent notificationPending;
    private Intent openApplication;
    public boolean stopDownload;
    private boolean aboveLollipopFlag;

    /**
     * Class constructor. for download manager
     *
     * @param context                    application context to show notification after finish
     * @param notificationDownloaderFlag flag to appear notification progressbar
     */
    public DownloadManager(Context context, boolean notificationDownloaderFlag) {

        this.context = context;
        this.notificationDownloaderFlag = notificationDownloaderFlag;
        init();
    }

    /**
     * Class constructor. for download manager
     *
     * @param downloadProgressBar        gui progressbar
     * @param notificationDownloaderFlag flag to appear notification progressbar
     */
    public DownloadManager(ProgressBar downloadProgressBar, boolean notificationDownloaderFlag) {
        this.downloadProgressBar = downloadProgressBar;
        this.notificationDownloaderFlag = notificationDownloaderFlag;
        init();
    }

    /**
     * Class constructor. for download manager
     *
     * @param context                    application context to show notification after finish
     * @param downloadProgressBar        gui progressbar
     * @param notificationDownloaderFlag flag to appear notification progressbar
     */
    public DownloadManager(Context context, ProgressBar downloadProgressBar, boolean notificationDownloaderFlag) {
        this.downloadProgressBar = downloadProgressBar;
        this.context = context;
        this.notificationDownloaderFlag = notificationDownloaderFlag;
        init();
    }

    /**
     * Class constructor. for download manager
     *
     * @param context                    application context to show notification after finish
     * @param downloadProgressBar        gui progressbar
     * @param downloadInfo               textview to show download information
     * @param notificationDownloaderFlag flag to appear notification progressbar
     */
    public DownloadManager(Context context, ProgressBar downloadProgressBar, TextView downloadInfo, boolean notificationDownloaderFlag) {
        this.context = context;
        this.downloadProgressBar = downloadProgressBar;
        this.downloadInfo = downloadInfo;
        this.notificationDownloaderFlag = notificationDownloaderFlag;
        init();
    }


    /**
     * Public constructor for download links
     *
     * @param context                    Application Context
     * @param notificationDownloaderFlag Flag to show notification
     * @param downloadLinks              List of download links
     */
    public DownloadManager(Context context, boolean notificationDownloaderFlag, List<String> downloadLinks) {

        this.context = context;
        this.downloadLinks = downloadLinks;
        this.notificationDownloaderFlag = notificationDownloaderFlag;
        init();
    }

    /**
     * Function to init download objects
     */
    private void init() {
        openApplication = new Intent(context, MainActivity.class);
        notificationPending = PendingIntent.getActivity(context, 0,
                openApplication, 0);
        aboveLollipopFlag = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;

    }

    /**
     * Start download file from url
     *
     * @param url the download url
     * @return flag download success or not
     */
    @Override
    protected Boolean doInBackground(String... url) {

        try {
            if (downloadLinks != null) {
                return multiDownload(downloadLinks, url[1]);
            } else {
                return singleDownload(url[0], url[1]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;

    }


    /**
     * Function to single download
     *
     * @param link             Download link
     * @param downloadLocation Download downloadLocation
     * @return Flag or download success or not
     * @throws IOException
     */
    public boolean  singleDownload(String link, String downloadLocation) throws IOException {
        //file name
        fileName = link.substring(link.lastIndexOf('/') + 1, link.length());
        filePath = downloadLocation; //url[1];
        String splits[] = fileName.split("\\.");
        fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());

        //divided update notification
        notificationDivider = 0;

        //new http connection
        OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.MINUTES).
                readTimeout(15, TimeUnit.MINUTES).build();

        Call call = httpClient.newCall(new Request.Builder().url(link).get().build());
        Response response = call.execute();

        if (notificationDownloaderFlag) showNotificationDownloader();

        //susses request
        if (response.code() == 200) {
            InputStream inputStream = null;
            OutputStream output = null;

            try {
                //path response to input stream
                inputStream = response.body().byteStream();
                output = new FileOutputStream(filePath + "/" + fileName);
                byte[] buffer = new byte[DOWNLOAD_CHUNK_SIZE];
                long download = 0;
                long target = response.body().contentLength();
                int oneBlock = Math.round((target / 100));
                int oneLoop = (oneBlock / DOWNLOAD_CHUNK_SIZE);
                //set progress zero and response length
                publishProgress(0L, target);
                while (true) {
                    //flag to stop download
                    if (stopDownload) break;

                    notificationDivider++;
                    //read buffer
                    int read = inputStream.read(buffer);
                    if (read == -1) break;
                    download += read;
                    if (notificationDivider == oneLoop) {
                        publishProgress(download, target);
                        notificationDivider = 0;
                    }
                    if (isCancelled()) break;
                    output.write(buffer, 0, read);
                }

                return download == target;

            } catch (IOException e) {
                e.printStackTrace();
                return false;
            } finally {
                if (inputStream != null) inputStream.close();
            }

        } else {
            return false;
        }
    }

    /**
     * Function to multi download
     *
     * @param links            List of links
     * @param downloadLocation Download destination
     * @return Flag of download success or not
     * @throws IOException
     */
    public synchronized boolean multiDownload(List<String> links, String downloadLocation) throws IOException {

        if (notificationDownloaderFlag) showNotificationDownloader();
        int counter = 0;
        publishProgress(0L, Long.valueOf(links.size()));
        //foreach for the all links
        for (String linkItem : links) {

            //flag to stop download
            if (stopDownload) break;

            //update progress
            publishProgress(Long.valueOf(counter++), Long.valueOf(links.size()));

            //file name
            fileName = linkItem.substring(linkItem.lastIndexOf('/') + 1, linkItem.length());
            filePath = downloadLocation;
            String splits[] = fileName.split("\\.");
            fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());

            //divided update notification
            notificationDivider = 0;

            //new http connection
            OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.MINUTES).
                    readTimeout(15, TimeUnit.MINUTES).build();

            Call call = httpClient.newCall(new Request.Builder().url(linkItem).get().build());
            Response response = call.execute();


            //susses request
            if (response.code() == 200) {
                InputStream inputStream = null;
                OutputStream output = null;

                try {
                    //path response to input stream
                    inputStream = response.body().byteStream();
                    output = new FileOutputStream(filePath + "/" + fileName);
                    byte[] buffer = new byte[DOWNLOAD_CHUNK_SIZE];
                    //set progress zero and response length

                    while (true) {

                        //flag to stop download
                        if (stopDownload) break;

                        notificationDivider++;
                        //read buffer
                        int read = inputStream.read(buffer);
                        if (read == -1) break;
                        if (isCancelled()) break;
                        output.write(buffer, 0, read);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                } finally {
                    if (inputStream != null) inputStream.close();
                }

            } else {
                return false;
            }
        }
        return true;

    }

    /**
     * Function show download information
     *
     * @param values download information values
     */
    @Override
    protected void onProgressUpdate(Long... values) {

        if (downloadProgressBar != null) {
            downloadProgressBar.setMax(values[1].intValue());
            downloadProgressBar.setProgress(values[0].intValue());
        }

        if (downloadInfo != null) {
            downloadInfo.setText(values[1] / 1000000 + "/" + values[0] / 1000000);
        }

        if (notificationDownloaderFlag) {
            //remoteViews.setProgressBar(R.id.progressBar2, , , false);
            builder.setProgress(values[1].intValue(), values[0].intValue(), false);
            notificationManager.notify(0, builder.build());
        }

        if (context != null) {
            if (context instanceof Service) {
                Intent i = new Intent(AppConstants.Download.INTENT);
                i.putExtra(AppConstants.Download.NUMBER, values[0]);
                i.putExtra(AppConstants.Download.MAX, values[1]);
                i.putExtra(AppConstants.Download.DOWNLOAD, AppConstants.Download.IN_DOWNLOAD);
                LocalBroadcastManager.getInstance(context).sendBroadcast(i);
            }

        }

    }

    /**
     * Action after download finished of not
     *
     * @param result flag download success or not
     */
    @Override
    protected void onPostExecute(Boolean result) {

        //notify download complete
        if (context != null)
            Toast.makeText(context, result == true ?
                            context.getString(R.string.download_complete) :
                            context.getString(R.string.download_failed)
                    , Toast.LENGTH_LONG).show();

        //pass the download statue to notification
        if (notificationDownloaderFlag) {
            notificationManager.cancel(0);
            if (result) {
                showCompleteNotification();
                AppPreference.DownloadStatues(AppConstants.Preferences.DOWNLOAD_SUCCESS);
            } else {
                showFailedNotification();
                AppPreference.DownloadStatues(AppConstants.Preferences.DOWNLOAD_FAILED);
            }
        }

        //run extraction service if zip file or stop service
        if (fileExtension.toLowerCase().equals("zip") && result) {
            new UnZipping(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, filePath, fileName);
        } else {
            //send broadcast of success or failed
            LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(AppConstants.Download.INTENT)
                    .putExtra(AppConstants.Download.DOWNLOAD, result == true ?
                            AppConstants.Download.SUCCESS :
                            AppConstants.Download.FAILED));
            if (context instanceof Service)
                ((Service) context).stopService(new Intent(context, DownloadService.class));
        }

    }

    /**
     * Initialize and show notification of download statue
     */
    public void showNotificationDownloader() {

        remoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_download_progress);
        builder = new NotificationCompat.Builder(context)
                .setSmallIcon(aboveLollipopFlag ? R.drawable.ic_quran_trans : R.drawable.logo)
                .setColor(Color.parseColor("#3E686A"))
                .setProgress(100, 0, false)
                .setContentTitle(context.getString(R.string.app_name))
                .setContentText(context.getString(R.string.download))
                .setOngoing(true);
        builder.setContentIntent(notificationPending);
        notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        notificationManager.notify(0, builder.build());
    }

    /**
     * Initialize and show notification of download completes
     */
    public void showCompleteNotification() {
        remoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_download_finished);
        builder = new NotificationCompat
                .Builder(context)
                .setSmallIcon(aboveLollipopFlag ? R.drawable.ic_quran_trans : R.drawable.logo)
                .setContentTitle(context.getString(R.string.app_name))
                .setContentText(context.getString(R.string.download_complete))
                .setColor(Color.parseColor("#3E686A"));
        builder.setContentIntent(notificationPending);
        notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        notificationManager.notify(0, builder.build());
    }

    /**
     * Initialize and show notification of download failed
     */
    public void showFailedNotification() {
        remoteViews = new RemoteViews(context.getPackageName(), R.layout.notification_download_failed);
        builder = new NotificationCompat
                .Builder(context)
                .setSmallIcon(aboveLollipopFlag ? R.drawable.ic_quran_trans : R.drawable.logo)
                .setContentTitle(context.getString(R.string.app_name))
                .setContentText(context.getString(R.string.download_failed))
                .setColor(Color.parseColor("#3E686A"));
        notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        notificationManager.notify(0, builder.build());
    }

}