/**
 * This file is part of Privacy Friendly Interval Timer.
 * Privacy Friendly Interval Timer 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 any later version.
 * Privacy Friendly Interval Timer 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 Privacy Friendly Interval Timer. If not, see <http://www.gnu.org/licenses/>.
 */

package org.secuso.privacyfriendlyintervaltimer.activities;

import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import org.secuso.privacyfriendlyintervaltimer.R;
import org.secuso.privacyfriendlyintervaltimer.services.TimerService;

import java.util.ArrayList;

/**
 * Workout view with a workout and rest timer.
 * Timers can be paused and skipped. Once the workout is finished a message is shown and
 * the view navigates back to the main view.
 *
 * @author Alexander Karakuz
 * @version 20170809
 * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
 */
public class WorkoutActivity extends AppCompatActivity {

    //General
    private SharedPreferences settings;

    // GUI Text
    private TextView currentSetsInfo = null;
    private TextView workoutTimer = null;
    private TextView workoutTitle = null;
    private TextView caloriesNumber = null;

    // GUI Buttons
    private FloatingActionButton fab = null;
    private ImageButton volumeButton = null;
    private ImageView prevTimer = null;
    private ImageView nextTimer = null;

    //GUI Elements
    private ProgressBar progressBar = null;
    private View finishedView = null;

    // Service variables
    private final BroadcastReceiver timeReceiver = new BroadcastReceiver();
    private TimerService timerService = null;
    private boolean serviceBound = false;


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_workout);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Bind to LocalService
        Intent intent = new Intent(this, TimerService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

        // Initialize all GUI variables
        this.caloriesNumber = (TextView) this.findViewById(R.id.workout_finished_calories_number);
        this.currentSetsInfo = (TextView) this.findViewById(R.id.current_sets_info);
        this.volumeButton = (ImageButton) this.findViewById(R.id.volume_button);
        this.prevTimer = (ImageView) this.findViewById(R.id.workout_previous);
        this.progressBar = (ProgressBar) this.findViewById(R.id.progressBar);
        this.workoutTimer = (TextView) this.findViewById(R.id.workout_timer);
        this.workoutTitle = (TextView) this.findViewById(R.id.workout_title);
        this.settings = PreferenceManager.getDefaultSharedPreferences(this);
        this.nextTimer = (ImageView) this.findViewById(R.id.workout_next);
        this.finishedView = findViewById(R.id.finishedView);
        this.finishedView.setVisibility(View.GONE);

        // Set the workout screen to remain on if so enabled in the settings
        if(isKeepScreenOnEnabled(this)) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }

        // Set the gui to workout gui colors if start timer wasn't enabled
        if(!isStartTimerEnabled(this)) {
            setWorkoutGuiColors(true);
        }

        // Register the function of the pause button
        fab = (FloatingActionButton) findViewById(R.id.fab_pause_resume);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                fab.setSelected(!fab.isSelected());
                if (fab.isSelected() && timerService != null){
                    fab.setImageResource(R.drawable.ic_play_48dp);
                    timerService.pauseTimer();
                } else if (timerService != null) {
                    fab.setImageResource(R.drawable.ic_pause_48dp);
                    timerService.resumeTimer();
                }
            }
        });

        // Set image and flag of the volume button according to the current sound settings
        int volumeImageId = isSoundsMuted(this) ? R.drawable.ic_volume_mute_24dp : R.drawable.ic_volume_loud_24dp;
        volumeButton.setImageResource(volumeImageId);
        volumeButton.setSelected(isSoundsMuted(this));

        // Register the function of the volume button
        volumeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                volumeButton.setSelected(!volumeButton.isSelected());
                if (volumeButton.isSelected()){
                    volumeButton.setImageResource(R.drawable.ic_volume_mute_24dp);
                    muteAllSounds(true);
                } else {
                    volumeButton.setImageResource(R.drawable.ic_volume_loud_24dp);
                    muteAllSounds(false);
                }
            }
        });

        //getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }


    /**
     * Defines callbacks for service binding, passed to bindService()
     * Performs an initial GUI update when connection is established.
     **/
    private ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            TimerService.LocalBinder binder = (TimerService.LocalBinder) service;
            timerService = binder.getService();
            serviceBound = true;

            timerService.setIsAppInBackground(false);
            updateGUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            serviceBound = false;
        }
    };


    /**
     * Receives the timer ticks from the service and updates the GUI accordingly
     **/
    public class BroadcastReceiver extends android.content.BroadcastReceiver {

        // Threshold when progressbar starts blinking in milliseconds and the blinking speed
        final int workoutBlinkingTime = 10000; //10 sec
        final int restBlinkingTime = 5000; // 5 sec

        // Timestamp to calculate when next progressBar color change should occur
        long oldTimeStamp = workoutBlinkingTime + 100;

        // Flags for the color switches of the GUI
        boolean workoutColors = false;

        /**
         * Updates the GUI depending on the message recived
         *
         * onTickMillis - Updates the progressBar progress according to current timer millis and makes it blink
         * timer_title - Updates the GUI title and switches the GUI colors accordingly
         * countdown_seconds - Updates the current seconds in the GUI
         * current_set - Updates the current sets in the GUI
         * workout_finished - Shows the final message and navigates back to the main view
         * new_timer - Resets the progressBar
         **/
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getExtras() != null) {
                if (intent.getLongExtra("onTickMillis", 0) != 0) {

                    long millis = intent.getLongExtra("onTickMillis", 0);
                    progressBar.setProgress((int)millis);

                    if (isProgressBarBlinking(millis, context)) {
                        progressBarBlink();
                    }
                }
                if (intent.getStringExtra("timer_title") != null) {
                    String message = intent.getStringExtra("timer_title");

                    workoutColors = message.equals(getResources().getString(R.string.workout_headline_workout));
                    setWorkoutGuiColors(workoutColors);
                    workoutTitle.setText(message);
                }
                if (intent.getIntExtra("countdown_seconds", -1) != -1) {
                    int seconds = intent.getIntExtra("countdown_seconds", 0);
                    workoutTimer.setText(Integer.toString(seconds));
                }
                if (intent.getIntExtra("current_set", 0) != 0 && intent.getIntExtra("sets", 0) != 0) {
                    int currentSet = intent.getIntExtra("current_set", 0);
                    int sets = intent.getIntExtra("sets", 0);

                    currentSetsInfo.setText(getResources().getString(R.string.workout_info) + ": " + Integer.toString(currentSet) + "/" + Integer.toString(sets));
                }
                if (intent.getBooleanExtra("workout_finished", false) != false) {
                    showFinishedView();
                }
                if (intent.getLongExtra("new_timer", 0) != 0) {
                    long timerDuration = intent.getLongExtra("new_timer", 0);
                    int seconds = (int) Math.ceil(timerDuration / 1000.0);
                    workoutTimer.setText(Integer.toString(seconds));
                    progressBar.setMax((int) timerDuration);
                    progressBar.setProgress((int) timerDuration);
                    this.oldTimeStamp = workoutBlinkingTime + workoutBlinkingTime;

                    progressBar.animate().cancel();
                    progressBar.setAlpha(1.0f);
                }
            }
        }

        /**
         * Check if the progressbar should be blinking
         *
         * @param millis The current milliseconds
         * @param context Context
         * @return Boolean if the progressbar should be blinking
         */
        private boolean isProgressBarBlinking(long millis, Context context){
            int buffer = 100; //Makes sure the animation has time to begin

            if (millis <= workoutBlinkingTime && workoutColors && isBlinkingProgressBarEnabled(context) && oldTimeStamp - buffer >= millis) {
                oldTimeStamp = millis;
                return true;
            } else if (millis <= restBlinkingTime && isBlinkingProgressBarEnabled(context) && oldTimeStamp - buffer >= millis) {
                oldTimeStamp = millis;
                return true;
            }
            return  false;
        }
    }

    /**
     * Switches between colors for rest timer and workout timer
     * according to the boolean flag provided/
     *
     * @param guiFlip True for workout phase colors, false for rest phase colors
     */
    private void setWorkoutGuiColors(boolean guiFlip) {
        int textColor = guiFlip ? R.color.white : R.color.black;
        int backgroundColor = guiFlip ? R.color.lightblue : R.color.white;
        int progressBackgroundColor = guiFlip ? R.color.white : R.color.lightblue;
        int buttonColor = guiFlip ? R.color.white : R.color.darkblue;


        currentSetsInfo.setTextColor(getResources().getColor(textColor));
        workoutTitle.setTextColor(getResources().getColor(textColor));
        workoutTimer.setTextColor(getResources().getColor(textColor));
        prevTimer.setColorFilter(getResources().getColor(buttonColor));
        nextTimer.setColorFilter(getResources().getColor(buttonColor));

        View view = findViewById(R.id.workout_content);
        view.setBackgroundColor(getResources().getColor(backgroundColor));

        LayerDrawable progressBarDrawable = (LayerDrawable) progressBar.getProgressDrawable();
        Drawable backgroundDrawable = progressBarDrawable.getDrawable(0);
        Drawable progressDrawable = progressBarDrawable.getDrawable(1);
        progressDrawable.setColorFilter(ContextCompat.getColor(this, R.color.colorPrimary), PorterDuff.Mode.SRC_IN);
        backgroundDrawable.setColorFilter(ContextCompat.getColor(this, progressBackgroundColor), PorterDuff.Mode.SRC_IN);

        //progressBar.setProgressBackgroundTintList(ColorStateList.valueOf(getResources().getColor(progressBackgroundColor)));
    }

    /**
     * Lets the progressbar blink by changing the alpha value
     */
    private void progressBarBlink() {
        if (progressBar.getAlpha() == 1.0f)
            progressBar.animate().alpha(0.1f).setDuration(600).start();
        else if(progressBar.getAlpha() <= 0.15f)
            progressBar.animate().alpha(1.0f).setDuration(600).start();
    }

    /**
     * Click functions to go to previous or next timer and to finish the current workout.
     * On workout finish an alert is build followed by a navigation to the main view.
     *
     * @param view View
     */
    public void onClick(View view) {
        switch(view.getId()) {
            case R.id.workout_previous:
                if(timerService != null){
                    timerService.prevTimer();
                }
                break;
            case R.id.workout_next:
                if(timerService != null){
                    timerService.nextTimer();
                }
                break;
            case R.id.workout_finished_ok:
                cleanTimerServiceFinish();
                finish();
                break;
            case R.id.finish_workout:
                if(isCancelDialogEnabled(this)){
                    showCancelAlert(true);
                }
                else {
                    showFinishedView();
                }
                break;
            default:
        }
    }


    /**
     * Update all GUI elements by getting the current timer values from the TimerService
     */
    private void updateGUI(){
        if(timerService != null){
            boolean isPaused = timerService.getIsPaused();
            int currentSet = timerService.getCurrentSet();
            String title = timerService.getCurrentTitle();
            long savedTime = timerService.getSavedTime();
            int sets = timerService.getSets();
            long timerDuration = 0;

            if(title.equals(getResources().getString(R.string.workout_headline_workout))){
                timerDuration = timerService.getWorkoutTime();
                setWorkoutGuiColors(true);
            }
            else if(title.equals(getResources().getString(R.string.workout_headline_start_timer))){
                timerDuration = timerService.getStartTime();
                setWorkoutGuiColors(false);
            }
            else if(title.equals(getResources().getString(R.string.workout_headline_rest))){
                timerDuration = timerService.getRestTime();
                setWorkoutGuiColors(false);
            }
            else if(title.equals(getResources().getString(R.string.workout_block_periodization_headline))){
                timerDuration = timerService.getBlockRestTime();
                setWorkoutGuiColors(false);
            }

            String time = Long.toString((int) Math.ceil(savedTime / 1000.0));

            currentSetsInfo.setText(getResources().getString(R.string.workout_info) +": "+Integer.toString(currentSet)+"/"+Integer.toString(sets));
            workoutTitle.setText(title);
            workoutTimer.setText(time);
            progressBar.setMax((int) timerDuration);
            progressBar.setProgress((int) savedTime);
            progressBar.setAlpha(1.0f);

            if (isPaused){
                fab.setImageResource(R.drawable.ic_play_48dp);
                fab.setSelected(true);
            }
            else {
                fab.setImageResource(R.drawable.ic_pause_48dp);
                fab.setSelected(false);
            }

            if(title.equals(getResources().getString(R.string.workout_headline_done))){
                showFinishedView();
            }
        }
    }


    /**
     * Build and show an AlertDialog for when the workout is canceled
     *
     */
    private void showCancelAlert(final boolean showFinish){
        if(timerService != null){
            timerService.pauseTimer();
            timerService.setCancelAlert(true);
        }

        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);

        final CharSequence[] item = {getResources().getString(R.string.workout_canceled_check)};
        final boolean[] selection = {false};
        final ArrayList selectedItem = new ArrayList();

        alertBuilder.setMultiChoiceItems(item, selection, new DialogInterface.OnMultiChoiceClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) {
                if (isChecked) {
                    selectedItem.add(indexSelected);
                    settings.edit().putBoolean(getString(R.string.pref_cancel_workout_check), false).commit();
                } else if (selectedItem.contains(indexSelected)) {
                    selectedItem.remove(Integer.valueOf(indexSelected));
                    settings.edit().putBoolean(getString(R.string.pref_cancel_workout_check), true).commit();
                }
            }
        });

        alertBuilder.setTitle(R.string.workout_canceled_info);

        alertBuilder.setNegativeButton(getString(R.string.alert_confirm_dialog_negative), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                if(timerService != null){
                    timerService.resumeTimer();
                    timerService.setCancelAlert(false);
                }
                updateGUI();
                dialog.dismiss();
            }
        });


        alertBuilder.setPositiveButton(getString(R.string.alert_confirm_dialog_positive), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                if(showFinish){
                    showFinishedView();
                }
                else {
                    cleanTimerServiceFinish();
                    finish();
                }
            }
        });

        alertBuilder.setOnCancelListener(new OnCancelListener() {

            public void onCancel(DialogInterface dialog) {
                if(timerService != null){
                    timerService.resumeTimer();
                    timerService.setCancelAlert(false);
                }
                updateGUI();
                dialog.dismiss();
            }
        });

        alertBuilder.create().show();
    }

    /**
     * Shows a transparent overlay over the workout screen.
     * Overlay displays that the workout is over and optionally how many calories were burned.
     */
    private void showFinishedView(){
        TextView finishButton = (TextView) this.findViewById(R.id.finish_workout);
        finishButton.setEnabled(false);

        if(timerService != null){
            timerService.setCurrentTitle(getString(R.string.workout_headline_done));
            timerService.pauseTimer();
        }
        this.workoutTitle.setText(getResources().getString(R.string.workout_headline_done));
        this.workoutTimer.setText("0");
        this.fab.hide();
        this.finishedView.setVisibility(View.VISIBLE);

        finishedView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });

        if(isCaloriesEnabled(this)){
            TextView txt = (TextView)findViewById(R.id.workout_finished_calories_text);
            TextView nmbr = (TextView)findViewById(R.id.workout_finished_calories_number);
            TextView unit = (TextView)findViewById(R.id.workout_finished_calories_unit);

            txt.setVisibility(View.VISIBLE);
            nmbr.setVisibility(View.VISIBLE);
            unit.setVisibility(View.VISIBLE);

            if(timerService != null){
                String caloriesBurned = Integer.toString(timerService.getCaloriesBurned());
                this.caloriesNumber.setText(caloriesBurned);
            }
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        // Unbind from the service
        if (serviceBound) {
            unbindService(serviceConnection);
            serviceBound = false;
        }
    }

    /**
     * Stop the notification and update the GUI with current values
     */
    @Override
    public void onResume() {
        super.onResume();

        if(timerService != null){
            timerService.setIsAppInBackground(false);
        }
        updateGUI();
        registerReceiver(timeReceiver, new IntentFilter(TimerService.COUNTDOWN_BROADCAST));
    }

    /**
     * Start the notification when activity goes into the background
     */
    @Override
    public void onPause() {
        super.onPause();

        if(timerService != null){
            timerService.setIsAppInBackground(true);
        }
        unregisterReceiver(timeReceiver);
    }

    /**
     * Stop the notification when activity is destroyed
     */
    @Override
    public void onDestroy() {
        if(timerService != null){
            timerService.workoutClosed();
            timerService.setCancelAlert(false);
        }
        super.onDestroy();
    }

    /*
     * Stop all timers and remove notification when navigating back to the main activity
     */
    @Override
    public void onBackPressed() {
        if(isCancelDialogEnabled(this)){
            showCancelAlert(false);
        }
        else{
            cleanTimerServiceFinish();
            finish();
        }
        //super.onBackPressed();
    }


    /**
     * Calls the service to stop an clear all timer
     */
    private void cleanTimerServiceFinish(){
        if(timerService != null) {
            timerService.cleanTimerFinish();
        }
    }

    /**
     * Mutes or unmutes all sound output
     *
     * @param mute Flag to mute or unmute all sounds
     */
    private void muteAllSounds(boolean mute){
        if(this.settings != null) {
            SharedPreferences.Editor editor = settings.edit();
            editor.putBoolean(getResources().getString(R.string.pref_sounds_muted), mute);
            editor.apply();
        }
    }

    /*
    * Multiple checks for what was enabled inside the settings
    */
    public boolean isKeepScreenOnEnabled(Context context){
        if(this.settings != null){
            return settings.getBoolean(context.getString(R.string.pref_keep_screen_on_switch_enabled), true);
        }
        return false;
    }

    public boolean isStartTimerEnabled(Context context) {
        if (this.settings != null) {
            return settings.getBoolean(context.getString(R.string.pref_start_timer_switch_enabled), true);
        }
        return false;
    }

    public boolean isBlinkingProgressBarEnabled(Context context) {
        if (this.settings != null) {
            return settings.getBoolean(context.getString(R.string.pref_blinking_progress_bar), false);
        }
        return false;
    }

    public boolean isCaloriesEnabled(Context context) {
        if (this.settings != null) {
            return settings.getBoolean(context.getString(R.string.pref_calories_counter), true);
        }
        return false;
    }

    public boolean isSoundsMuted(Context context) {
        if (this.settings != null) {
            return settings.getBoolean(context.getString(R.string.pref_sounds_muted), true);
        }
        return true;
    }

    public boolean isCancelDialogEnabled(Context context) {
        if (this.settings != null) {
            return settings.getBoolean(context.getString(R.string.pref_cancel_workout_check), true);
        }
        return true;
    }
}