/*
 * Copyright (C) 2020 Team Gateship-One
 * (Hendrik Borghorst & Frederik Luetkes)
 *
 * The AUTHORS.md file contains a detailed contributors list:
 * <https://github.com/gateship-one/odyssey/blob/master/AUTHORS.md>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.gateshipone.odyssey.artwork;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;

import com.android.volley.NetworkResponse;
import com.android.volley.VolleyError;

import org.gateshipone.odyssey.BuildConfig;
import org.gateshipone.odyssey.R;
import org.gateshipone.odyssey.artwork.network.ArtworkRequestModel;
import org.gateshipone.odyssey.artwork.network.ImageResponse;
import org.gateshipone.odyssey.artwork.network.InsertImageTask;
import org.gateshipone.odyssey.artwork.network.artprovider.ArtProvider;
import org.gateshipone.odyssey.artwork.storage.ArtworkDatabaseManager;
import org.gateshipone.odyssey.artwork.storage.ImageNotFoundException;
import org.gateshipone.odyssey.models.AlbumModel;
import org.gateshipone.odyssey.models.ArtistModel;
import org.gateshipone.odyssey.utils.MusicLibraryHelper;
import org.gateshipone.odyssey.utils.NetworkUtils;
import org.json.JSONException;

import java.util.LinkedList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

public class BulkDownloadService extends Service implements InsertImageTask.ImageSavedCallback, ArtProvider.ArtFetchError {
    private static final String TAG = BulkDownloadService.class.getSimpleName();

    private static final int NOTIFICATION_ID = 84;

    private static final String NOTIFICATION_CHANNEL_ID = "BulkDownloader";

    public static final String ACTION_CANCEL_BULKDOWNLOAD = "org.gateshipone.odyssey.bulkdownload.cancel";

    public static final String ACTION_START_BULKDOWNLOAD = "org.gateshipone.odyssey.bulkdownload.start";

    public static final String BUNDLE_KEY_ARTIST_PROVIDER = "org.gateshipone.odyssey.artist_provider";

    public static final String BUNDLE_KEY_ALBUM_PROVIDER = "org.gateshipone.odyssey.album_provider";

    public static final String BUNDLE_KEY_WIFI_ONLY = "org.gateshipone.odyssey.wifi_only";

    public static final String BUNDLE_KEY_USE_LOCAL_IMAGES = "org.gateshipone.odyssey.use_local_images";

    private NotificationManager mNotificationManager;

    private NotificationCompat.Builder mBuilder;

    private int mSumArtworkRequests;

    private ActionReceiver mBroadcastReceiver;

    private PowerManager.WakeLock mWakelock;

    private ConnectionStateReceiver mConnectionStateChangeReceiver;

    private boolean mWifiOnly;

    private boolean mUseLocalImages;

    final private LinkedList<ArtworkRequestModel> mArtworkRequestQueue = new LinkedList<>();

    private ArtworkManager mArtworkManager;

    private ArtworkDatabaseManager mDatabaseManager;

    /**
     * Called when the service is created because it is requested by an activity
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);

        mConnectionStateChangeReceiver = new ConnectionStateReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        registerReceiver(mConnectionStateChangeReceiver, filter);
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(mBroadcastReceiver);
        unregisterReceiver(mConnectionStateChangeReceiver);

        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null && ACTION_START_BULKDOWNLOAD.equals(intent.getAction())) {
            if (BuildConfig.DEBUG) {
                Log.v(TAG, "Starting bulk download in service with thread id: " + Thread.currentThread().getId());
            }

            // reset counter
            mSumArtworkRequests = 0;

            String artistProvider = getString(R.string.pref_artwork_provider_artist_default);
            String albumProvider = getString(R.string.pref_artwork_provider_album_default);
            mWifiOnly = true;

            // read setting from extras
            Bundle extras = intent.getExtras();
            if (extras != null) {
                artistProvider = extras.getString(BUNDLE_KEY_ARTIST_PROVIDER, getString(R.string.pref_artwork_provider_artist_default));
                albumProvider = extras.getString(BUNDLE_KEY_ALBUM_PROVIDER, getString(R.string.pref_artwork_provider_album_default));
                mWifiOnly = intent.getBooleanExtra(BUNDLE_KEY_WIFI_ONLY, true);
                mUseLocalImages = intent.getBooleanExtra(BUNDLE_KEY_USE_LOCAL_IMAGES, false);
            }

            if (artistProvider.equals(getString(R.string.pref_artwork_provider_none_key)) && albumProvider.equals(getString(R.string.pref_artwork_provider_none_key))) {
                return START_NOT_STICKY;
            }

            if (!NetworkUtils.isDownloadAllowed(this, mWifiOnly)) {
                return START_NOT_STICKY;
            }

            PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
            mWakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "odyssey:wakelock:bulkdownloader");

            // FIXME do some timeout checking. e.g. 5 minutes no new image then cancel the process
            mWakelock.acquire();

            mArtworkManager = ArtworkManager.getInstance(getApplicationContext());
            mArtworkManager.initialize(artistProvider, albumProvider, mWifiOnly, mUseLocalImages);

            mDatabaseManager = ArtworkDatabaseManager.getInstance(getApplicationContext());

            runAsForeground();

            createArtworkRequestQueue(!albumProvider.equals(getApplicationContext().getString((R.string.pref_artwork_provider_none_key))),
                    !artistProvider.equals(getApplicationContext().getString((R.string.pref_artwork_provider_none_key))));
        }
        return START_STICKY;
    }

    @Override
    public void onImageSaved(final ArtworkRequestModel artworkRequestModel, final Context applicationContext) {
        mArtworkManager.onImageSaved(artworkRequestModel, applicationContext);

        performNextRequest();
    }

    @Override
    public void fetchJSONException(final ArtworkRequestModel model, final Context context, final JSONException exception) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, "JSONException fetching: " + model.getLoggingString());
        }

        ImageResponse imageResponse = new ImageResponse();
        imageResponse.model = model;
        imageResponse.image = null;
        imageResponse.url = null;
        new InsertImageTask(context, this).execute(imageResponse);
    }

    @Override
    public void fetchVolleyError(final ArtworkRequestModel model, final Context context, final VolleyError error) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, "VolleyError for request: " + model.getLoggingString());
        }

        if (error != null) {
            NetworkResponse networkResponse = error.networkResponse;
            if (networkResponse != null && networkResponse.statusCode == 503) {
                finishedLoading();
                return;
            }
        }

        ImageResponse imageResponse = new ImageResponse();
        imageResponse.model = model;
        imageResponse.image = null;
        imageResponse.url = null;
        new InsertImageTask(context, this).execute(imageResponse);
    }

    public void fetchError(final ArtworkRequestModel model, final Context context) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, "JSONException fetching: " + model.getLoggingString());
        }

        ImageResponse imageResponse = new ImageResponse();
        imageResponse.model = model;
        imageResponse.image = null;
        imageResponse.url = null;
        new InsertImageTask(context, this).execute(imageResponse);
    }


    private void runAsForeground() {
        if (mBroadcastReceiver == null) {
            mBroadcastReceiver = new ActionReceiver();

            // Create a filter to only handle certain actions
            IntentFilter intentFilter = new IntentFilter();

            intentFilter.addAction(ACTION_CANCEL_BULKDOWNLOAD);

            registerReceiver(mBroadcastReceiver, intentFilter);
        }

        mBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setContentTitle(getString(R.string.downloader_notification_initialize))
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText(getString(R.string.downloader_notification_remaining_images) + ' ' + 0))
                .setProgress(0, 0, false)
                .setSmallIcon(R.drawable.odyssey_notification);

        openChannel();

        mBuilder.setOngoing(true);

        // Cancel action
        Intent nextIntent = new Intent(BulkDownloadService.ACTION_CANCEL_BULKDOWNLOAD);
        PendingIntent nextPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 1, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        androidx.core.app.NotificationCompat.Action cancelAction = new androidx.core.app.NotificationCompat.Action.Builder(R.drawable.ic_close_24dp, getString(R.string.dialog_action_cancel), nextPendingIntent).build();

        mBuilder.addAction(cancelAction);

        Notification notification = mBuilder.build();
        startForeground(NOTIFICATION_ID, notification);
        mNotificationManager.notify(NOTIFICATION_ID, notification);
    }

    private void createArtworkRequestQueue(final boolean fetchAlbums, final boolean fetchArtists) {
        mArtworkRequestQueue.clear();

        if (fetchAlbums) {
            List<AlbumModel> albums = MusicLibraryHelper.getAllAlbums(getApplicationContext());

            for (AlbumModel album : albums) {
                mArtworkRequestQueue.add(new ArtworkRequestModel(album));
            }
        }

        if (fetchArtists) {
            List<ArtistModel> artists = MusicLibraryHelper.getAllArtists(false, getApplicationContext());

            for (ArtistModel artist : artists) {
                mArtworkRequestQueue.add(new ArtworkRequestModel(artist));
            }
        }

        startBulkDownload();
    }

    private void startBulkDownload() {
        if (BuildConfig.DEBUG) {
            Log.v(TAG, "Bulkloading started with: " + mArtworkRequestQueue.size());
        }

        mSumArtworkRequests = mArtworkRequestQueue.size();

        mBuilder.setContentTitle(getString(R.string.downloader_notification_remaining_images));

        if (mArtworkRequestQueue.isEmpty()) {
            finishedLoading();
        } else {
            performNextRequest();
        }
    }

    private void performNextRequest() {
        ArtworkRequestModel requestModel;
        while (true) {
            synchronized (mArtworkRequestQueue) {
                updateNotification(mArtworkRequestQueue.size());

                requestModel = mArtworkRequestQueue.pollFirst();
            }

            if (requestModel != null) {
                if (checkRequest(requestModel)) {
                    createRequest(requestModel);
                    return;
                }
            } else {
                finishedLoading();
                return;
            }
        }
    }

    private boolean checkRequest(@NonNull final ArtworkRequestModel requestModel) {
        switch (requestModel.getType()) {
            case ALBUM: {
                AlbumModel album = (AlbumModel) requestModel.getGenericModel();
                if (mUseLocalImages || album.getAlbumArtURL() == null || album.getAlbumArtURL().isEmpty()) {
                    try {
                        mDatabaseManager.getAlbumImage(getApplicationContext(), album);
                    } catch (ImageNotFoundException e) {
                        return true;
                    }
                }
            }
            break;
            case ARTIST: {
                try {
                    mDatabaseManager.getArtistImage(getApplicationContext(), (ArtistModel) requestModel.getGenericModel());
                } catch (ImageNotFoundException e) {
                    return true;
                }
            }
            break;
        }
        return false;
    }

    private void createRequest(final ArtworkRequestModel requestModel) {
        switch (requestModel.getType()) {
            case ALBUM:
                mArtworkManager.fetchImage((AlbumModel) requestModel.getGenericModel(), getApplicationContext(), this, this);
                break;
            case ARTIST:
                mArtworkManager.fetchImage((ArtistModel) requestModel.getGenericModel(), getApplicationContext(), this, this);
                break;
        }

    }

    private void finishedLoading() {
        mArtworkRequestQueue.clear();

        ArtworkManager.getInstance(getApplicationContext()).cancelAllRequests(getApplicationContext());

        mNotificationManager.cancel(NOTIFICATION_ID);
        stopForeground(true);
        stopSelf();
        if (mWakelock.isHeld()) {
            mWakelock.release();
        }
    }

    private void updateNotification(final int pendingRequests) {
        if (BuildConfig.DEBUG) {
            Log.v(TAG, "Remaining requests: " + pendingRequests);
        }

        int finishedRequests = mSumArtworkRequests - pendingRequests;

        if (finishedRequests % 10 == 0) {
            mBuilder.setProgress(mSumArtworkRequests, finishedRequests, false);
            mBuilder.setStyle(new NotificationCompat.BigTextStyle()
                    .bigText(getString(R.string.downloader_notification_remaining_images) + ' ' + finishedRequests + '/' + mSumArtworkRequests));
            mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
        }
    }

    /**
     * Opens a notification channel and disables the LED and vibration
     */
    private void openChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, this.getResources().getString(R.string.notification_channel_downloader), android.app.NotificationManager.IMPORTANCE_LOW);
            // Disable lights & vibration
            channel.enableVibration(false);
            channel.enableLights(false);
            channel.setVibrationPattern(null);

            // Allow lockscreen control
            channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);

            // Register the channel
            mNotificationManager.createNotificationChannel(channel);
        }
    }

    private class ActionReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (BuildConfig.DEBUG) {
                Log.e(TAG, "Broadcast requested");
            }

            if (ACTION_CANCEL_BULKDOWNLOAD.equals(intent.getAction())) {
                if (BuildConfig.DEBUG) {
                    Log.e(TAG, "Cancel requested");
                }

                finishedLoading();
            }
        }
    }

    private class ConnectionStateReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (!NetworkUtils.isDownloadAllowed(context, mWifiOnly)) {
                if (BuildConfig.DEBUG) {
                    Log.v(TAG, "Cancel all downloads because of connection change");
                }

                // Cancel all downloads
                finishedLoading();
            }

        }
    }
}