/*
 * DSBDirect
 * Copyright (C) 2019 Fynn Godau
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * This software is not affiliated with heinekingmedia GmbH, the
 * developer of the DSB platform.
 */

package godau.fynn.dsbdirect;

import android.app.*;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
import humanize.Humanize;
import org.json.JSONException;

import com.android.volley.Response;
import com.android.volley.VolleyError;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;

@RequiresApi(LOLLIPOP)
public class PollingService extends JobService {
    private static final String CHANNEL = "plan_update";

    private static final int NOTIFICATION_ID = 70700707; // A random number
    private Handler mDoNothingHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });
    private Response.ErrorListener mErrorListener;

    private Utility u;

    @Override
    public boolean onStartJob(JobParameters parameters) {
        u = new Utility(PollingService.this);
        final SharedPreferences sharedPreferences = u.getSharedPreferences();
        final String credentialsId = sharedPreferences.getString("id", "162161");
        final String credentialsPass = sharedPreferences.getString("pass", "avhMOBIL1App");
        String token = sharedPreferences.getString("token", "00000000-0000-0000-0000-000000000000");

        final DownloadManager downloadManager = new DownloadManager(this);

        // Not nice code

        // On timetable downloaded
        final Handler timetableDownloadedHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                // Content is available

                String json = msg.getData().getString(DownloadManager.RESPONSE_JSON);

                // Parse tables
                try {
                    final Table[] tables = Reader.readTableList(json);

                    // Download every table

                    final int[] downloadedTables = {0}; // That's a trick my IDE taught me
                    final ArrayList<Entry> entries = new ArrayList<>();


                    for (final Table table :
                            tables) {
                        // Only proceed if table is html because we don't want to poll non-html
                        if (table.isHtml()) {

                            final FileManager fileManager = new FileManager(PollingService.this);
                            fileManager.getFile(table, downloadManager, new Handler(new Handler.Callback() {
                                @Override
                                public boolean handleMessage(Message msg) {
                                    Reader reader = u.getReader(fileManager.readHtmlFile(table), credentialsId);
                                    try {
                                        entries.addAll(
                                                reader.filterToday(
                                                reader.filterUserFilters(
                                                        reader.read(), PollingService.this)));

                                        downloadedTables[0]++;

                                        if (downloadedTables[0] == tables.length) {
                                            // We finally have all our entries
                                            createNotification(table, entries);
                                        }

                                    } catch (ParseException | NullPointerException e) {
                                        e.printStackTrace();
                                    }

                                    return false;
                                }
                            }), mErrorListener);
                        } else {
                            // TODO If it is not html, the user should know that their polling configuration is invalid
                            // TODO Let's hope nobody has a mix of html and non-html tables
                        }
                    }

                } catch (JSONException e) {
                    e.printStackTrace();
                }

                return false;
            }
        });

        // Try downloading timetable
        downloadManager.downloadTimetableList(token, new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == DownloadManager.RESPONSE_NO_CONTENT_REASON_NO_CONTENT) {
                    // No content is available

                    // Downloading a fresh token should fix the problem
                    downloadManager.downloadToken(credentialsId, credentialsPass, mDoNothingHandler, new Handler(new Handler.Callback() {
                        @Override
                        public boolean handleMessage(Message msg) {
                            // Correct token is fetched

                            String token = msg.getData().getString(DownloadManager.RESPONSE_TOKEN);

                            u.saveAuthToken(token);
                            downloadManager.downloadTimetableList(token, mDoNothingHandler, timetableDownloadedHandler,
                                    null);

                            return false;
                        }
                    }), mErrorListener);
                }

                return false;
            }
        }), timetableDownloadedHandler, mErrorListener);

        // Schedule next polling
        u.schedulePolling();


        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    private void createNotification(Table table, ArrayList<Entry> entries) {
        // The entries have finally been received

        SharedPreferences sharedPreferences = u.getSharedPreferences();


        // Create notification text
        StringBuilder text = new StringBuilder();

        if (entries.size() == 0) {
            // There are no entries

            // Get configure behaviour
            boolean displayEmptyNotifications = sharedPreferences.getBoolean("displayEmpty", false);
            if (displayEmptyNotifications) {
                text.append(getString(R.string.notification_plan_updates_empty));
            } else {
                // Cancel notification creation
                return;
            }
        } else {
            // Some entries are present
            for (int i = 0; i < entries.size(); i++) {
                Entry entry = entries.get(i);

                text.append(Utility.smartConcatenate(
                        new String[]{
                                entry.getAffectedClass(),
                                entry.getLesson(),
                                entry.getReplacementTeacher(),
                                entry.getInfo()
                        }, " · "
                ));

                if (i != entries.size() - 1) {
                    // This is not the last iteration yet
                    text.append("\n");
                }

            }
        }


        // Only notify if text is different from the last notification

        String lastText = sharedPreferences.getString("lastNotification", "Attention: the developer is programming late at night again");

        if (text.toString().equals(lastText)
                && sharedPreferences.getBoolean(Utility.SUPER_SECRET_SETTING_NOTIFY_ABOUT_NOTHING_NEW, false) == false // For debugging
        ) {
            // Notification already displayed
            Log.d("POLLINGNOTIFICATION", "notification not displayed because of text match (" + text.toString() + ")");
            return;
        }

        // Notification can be displayed

        // Store text to shared preferences
        sharedPreferences.edit().putString("lastNotification", text.toString()).apply();

        // Create content text / summary
        String contentText;
        if (sharedPreferences.getBoolean(Utility.SUPER_SECRET_SETTING_TEXT_AS_CONTENT_TEXT, false)) { // Developer has special needs, so he implemented a super secret setting
            contentText = text.toString().replaceAll("\n", " – ");
        } else {
            contentText = Humanize.pluralize(
                    getString(R.string.notification_plan_updates_text_pluralize_entries_one),
                    getString(R.string.notification_plan_updates_text_pluralize_entries_many),
                    getString(R.string.notification_plan_updates_empty),
                    entries.size());
        }


        // Create notification channel because API 26 demands it
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL, getString(R.string.notification_channel_plan_updates), importance);
            channel.setDescription(getString(R.string.notification_channel_plan_updates_description));

            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }

        // Expire notification at 0 o'clock
        Calendar timeoutAt = Calendar.getInstance();
        timeoutAt.add(Calendar.DAY_OF_YEAR, 1);
        Utility.zeroOClock(timeoutAt);
        // Timeout is then minus now
        long timeoutAfter = timeoutAt.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();

        // Create notification
        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, new NotificationCompat.Builder(PollingService.this, CHANNEL)
                .setSmallIcon(R.drawable.ic_notification)
                .setContentTitle(getString(R.string.notification_plan_updates_title))
                .setContentText(contentText)
                .setContentIntent(PendingIntent.getActivity(this, 2201,
                        new Intent(PollingService.this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
                .setStyle(
                        new NotificationCompat.BigTextStyle().bigText(text))
                .setColorized(true)
                .setColor(Color.argb(255, 63, 81, 181))
                .setTimeoutAfter(timeoutAfter)
                .build()
        );
    }

    // Methods for getting the next polling time

    public static long getNextPollingTime(SharedPreferences sharedPreferences) {
        return getNextPollingTime(sharedPreferences, Calendar.getInstance());
    }

    public static long getNextPollingTime(SharedPreferences sharedPreferences, Calendar now) {

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(now.getTimeInMillis());

        String pollingIntervalString = sharedPreferences.getString("pollingInterval", "5");

        try {
            calendar.add(Calendar.MINUTE, Integer.parseInt(pollingIntervalString));

            // If super secret setting for polling on weekends is not enabled, don't poll on weekends
            if (sharedPreferences.getBoolean(Utility.SUPER_SECRED_SETTING_POLL_ON_WEEKENDS, false) == false) {

                // If this is a weekend, go to next Monday
                switch (calendar.get(Calendar.DAY_OF_WEEK)) {
                    case Calendar.SATURDAY:
                        // If this a Saturday, go to Monday zero o' clock
                        calendar.add(Calendar.DAY_OF_MONTH, 2);
                        Utility.zeroOClock(calendar);
                        break;
                    case Calendar.SUNDAY:
                        // If this is a Sunday, go to tomorrow zero o' clock
                        calendar.add(Calendar.DAY_OF_MONTH, 1);
                        Utility.zeroOClock(calendar);
                }

            }

            // If this is late at night, go to the morning
            if (calendar.get(Calendar.HOUR_OF_DAY) < 5) {
                Utility.zeroOClock(calendar);
                calendar.set(Calendar.HOUR_OF_DAY, 5);
            }

            Log.d("POLLINGTIME", calendar.getTimeInMillis() + ", which is in " + (calendar.getTimeInMillis() - now.getTimeInMillis()));

            return calendar.getTimeInMillis();

        } catch (NumberFormatException e) {
            // There was no int, whatever
        }
        return 0;
    }

    public static long getNextPollingTimeDelay(SharedPreferences sharedPreferences) {
        return getNextPollingTimeDelay(sharedPreferences, Calendar.getInstance());
    }

    public static long getNextPollingTimeDelay(SharedPreferences sharedPreferences, Calendar now) {
        long nextTime = getNextPollingTime(sharedPreferences, now);
        return nextTime - now.getTimeInMillis();
    }
}
