/*
 * 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.Activity;
import android.app.AlertDialog;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.*;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StyleRes;
import com.google.android.material.snackbar.Snackbar;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import godau.fynn.dsbdirect.manager.DownloadManager;
import godau.fynn.dsbdirect.table.PollingService;
import godau.fynn.dsbdirect.table.reader.*;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

import static android.content.Context.MODE_PRIVATE;

public class Utility {

    public static final String DATE_FORMAT = "EEEE, d.M.yyyy";

    /**
     * For Gadgetbridge compatibility: Gadgetbridge seems to only push content text, not big text text
     */
    public static final String SUPER_SECRET_SETTING_TEXT_AS_CONTENT_TEXT = "textAsContentText";

    /**
     * For debugging: display messages even if the exact same content has already been displayed
     */
    public static final String SUPER_SECRET_SETTING_NOTIFY_ABOUT_NOTHING_NEW = "ignoreAlreadyDisplayed";

    /**
     * For debugging: also poll on weekends
     */
    public static final String SUPER_SECRET_SETTING_POLL_ON_WEEKENDS = "ignoreWeekend";

    /**
     * For convenience: in case you find sharing annoying, you can disable it
     */

    public static final String SUPER_SECRET_SETTING_HOLD_TO_SHARE = "holdToShare";

    /**
     * For remembering which school you're at: display school name as window title even if there is only one login
     */
    public static final String SUPER_SECRET_SETTING_FORCE_SCHOOL_NAME_AS_WINDOW_TITLE = "schoolNameAsWindowTitle";

    private Context mContext;

    public Utility(Context context) {
        this.mContext = context;
    }

    public String formatDate(Date date) {
        Calendar today = Calendar.getInstance();

        Calendar then = Calendar.getInstance();
        then.setTime(date);

        if (then.get(Calendar.YEAR) == today.get(Calendar.YEAR)
        && then.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.today);
        }

        Calendar tomorrow = Calendar.getInstance();
        tomorrow.add(Calendar.DAY_OF_YEAR, 1);

        if (then.get(Calendar.YEAR) == tomorrow.get(Calendar.YEAR)
                && then.get(Calendar.DAY_OF_YEAR) == tomorrow.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.tomorrow);
        }

        Calendar yesterday = Calendar.getInstance();
        yesterday.add(Calendar.DAY_OF_YEAR, -1);

        if (then.get(Calendar.YEAR) == yesterday.get(Calendar.YEAR)
                && then.get(Calendar.DAY_OF_YEAR) == yesterday.get(Calendar.DAY_OF_YEAR)) {
            return mContext.getString(R.string.yesterday);
        }

        // Date is neither today, yesterday nor tomorrow, format it

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault());
        return simpleDateFormat.format(date);
    }

    public @Nullable Reader getReader(String html, String id) {
        return ReaderFactory.getReader(html, id, mContext);
    }

    /**
     * Parse a natural date as provided as a last updated date
     * @param input A date in the format MM.dd.yyyy HH:mm
     * @return A Date
     */
    public static Date parseLastUpdatedDate(String input) {
        String[] dateParts = input.split(" ");
        String[] dateDigits = dateParts[0].split("\\.");
        String[] timeDigits = dateParts[1].split(":");
        return new Date(Integer.parseInt(dateDigits[2]) - 1900 /* years start at 1900 */, Integer.parseInt(dateDigits[1]) - 1 /* months start with 0 */,
                Integer.parseInt(dateDigits[0]), Integer.parseInt(timeDigits[0]), Integer.parseInt(timeDigits[1])); // because SimpleDateTime did deliver good results, again
    }

    public void checkForUpdate(final boolean verbose, final DownloadManager downloadManager) {
        // Thanks, https://stackoverflow.com/a/5069354
        final Activity activity = (Activity) mContext;
        final View rootView = ((ViewGroup) activity
                .findViewById(android.R.id.content)).getChildAt(0);

        Log.d("LOCALE", Locale.getDefault().toString());

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // Request current version metadata
                    JSONObject jsonObject = downloadManager.downloadUpdateCheck();

                    int currentVersion = BuildConfig.VERSION_CODE;

                    // Let's parse

                    int latestVersion = jsonObject.getInt("versionCode");
                    final String versionName = jsonObject.getString("versionName");
                    final String whatsNew;

                    // Localize changelog to German (hardcoded and uncool)
                    if (Locale.getDefault().toString().contains("de")) {
                        whatsNew = jsonObject.getString("whatsNewDE");
                    } else {
                        whatsNew = jsonObject.getString("whatsNew");
                    }

                    final String uri = jsonObject.getString("url");

                    if (latestVersion > currentVersion) {
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                new AlertDialog.Builder(mContext)
                                        .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                Intent intent;
                                                switch (mContext.getString(R.string.update_method)) {
                                                    default:
                                                    case "self":
                                                        // Download file provided in updateCheck
                                                        intent = new Intent(Intent.ACTION_VIEW)
                                                                .setDataAndType(Uri.parse(uri), "application/vnd.android.package-archive");
                                                        break;
                                                    case "fdroid":
                                                        // Launch fdroid website link and hope fdroid client handles it
                                                        String fdroidUri = mContext.getString(R.string.uri_fdroid, mContext.getPackageName());
                                                        intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fdroidUri));
                                                        break;
                                                    case "market":
                                                        // Launch market to download update
                                                        String marketUri = mContext.getString(R.string.uri_market, mContext.getPackageName());
                                                        intent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
                                                        break;
                                                }
                                                try {
                                                    mContext.startActivity(intent);
                                                } catch (ActivityNotFoundException e) {
                                                    mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(uri)));
                                                }
                                            }
                                        })
                                        .setNegativeButton(R.string.dismiss, null)
                                        .setTitle(mContext.getString(R.string.update_popup_title, versionName))
                                        .setMessage(whatsNew)
                                        .show();
                            }
                        });

                    } else {
                        if (verbose) {
                            activity.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Snackbar
                                            .make(rootView, R.string.up_to_date, Snackbar.LENGTH_SHORT)
                                            .show();
                                }
                            });
                        }
                    }

                } catch (IOException | JSONException e) {
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Snackbar.make(rootView, R.string.network_versioncheck_failed, Snackbar.LENGTH_SHORT)
                                    .show();
                        }
                    });
                    e.printStackTrace();
                }

            }
        }).start();

    }

    public SharedPreferences getSharedPreferences() {
        return mContext.getSharedPreferences("default", MODE_PRIVATE);
    }

    public String getUpdateCheckFrequency() {
        return getSharedPreferences().getString("update_check_frequency",
                mContext.getString(R.string.update_check_frequency_default));
    }

    public boolean shouldCheckForUpdates() {
        switch (getUpdateCheckFrequency()) {
            case "always":
                return true;
            case "sometimes":
                return new Random().nextInt(10) == 0;
            case "never":
            default:
                return false;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void schedulePolling() {
        // Check whether polling is enabled
        SharedPreferences sharedPreferences = getSharedPreferences();
        if (sharedPreferences.getBoolean("poll", false)) {
            // Polling is enabled

            ComponentName serviceComponent = new ComponentName(mContext, PollingService.class);
            JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
            builder.setMinimumLatency(PollingService.getNextPollingTimeDelay(sharedPreferences)); // Poll after at least this time
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // Only execute once network is connected
            builder.setPersisted(true);
            JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.schedule(builder.build());
        } else {
            // Cancel potential job
            JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.cancel(0);
        }
    }

    /**
     * @return A String containing String representations of all array elements separated by comma
     */
    public static <T> String smartConcatenate(T[] array, String comma) {
        StringBuilder result = new StringBuilder();

        // Remove empty strings
        ArrayList<T> list = new ArrayList<>(Arrays.asList(array));
        for (int i = list.size() - 1; i >= 0; i--) {
            if (list.get(i) == null || list.get(i).equals("") || list.get(i).equals("<strike></strike>")) {
                list.remove(i);
            }
        }

        // Append remaining strings to result
        for (int i = 0; i < list.size(); i++) {
            T t = list.get(i);

            if (t instanceof Element) {
                result.append(((Element) t).text().replaceAll("\\\\n", "\n"));
            } else
                result.append(t.toString());

            if (i != list.size() - 1) {
                // This is not the last iteration yet
                result.append(comma);
            }

        }

        return String.valueOf(result);
    }

    public static Calendar zeroOClock(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar;
    }

    /**
     * @author https://stackoverflow.com/a/9904844
     */
    public int dpToPx(int dp) {
        // Get the screen's density scale
        final float scale = mContext.getResources().getDisplayMetrics().density;
        // Convert the dps to pixels, based on density scale
        return (int) (dp * scale + 0.5f);
    }

    public @StyleRes
    int getStyle() {
        String preference = getSharedPreferences().getString("style", "light");

        switch (preference) {
            case "light":
            default:
                return R.style.Light;
            case "dark":
                return R.style.Dark;
            case "black":
                return R.style.Black;
        }
    }

    public @ColorInt int getColorPrimary() {
        return getSharedPreferences().getInt("colorPrimary",
                mContext.getResources().getColor(R.color.colorPrimary));
    }

    public @ColorInt int getColorPrimaryDark() {
        return getSharedPreferences().getInt("colorPrimaryDark",
                mContext.getResources().getColor(R.color.colorPrimaryDark));
    }

    public @ColorInt int getColorAccent() {
        return getSharedPreferences().getInt("colorAccent",
                mContext.getResources().getColor(R.color.colorAccent));
    }

    public void stylize() {
        mContext.setTheme(getStyle());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            @ColorInt int colorPrimaryDark = getColorPrimaryDark();
            Window window = ((Activity) mContext).getWindow();
            window.setNavigationBarColor(colorPrimaryDark);
            window.setStatusBarColor(colorPrimaryDark);
        }
    }


}
