package com.sudogeeks.talking_reminder;

import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.bignerdranch.android.multiselector.ModalMultiSelectorCallback;
import com.bignerdranch.android.multiselector.MultiSelector;
import com.bignerdranch.android.multiselector.SwappingHolder;
import com.firebase.ui.auth.AuthUI;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * \brief Main Activity class
 * This activity is the entry point of the app and is responsible for handling User Authentication and Landing page view
 */
public class MainActivity extends AppCompatActivity {
    private static final int RC_SIGN_IN = 123; // Choose an arbitrary request code value
    private RecyclerView reminderListView;
    private SimpleAdapter mAdapter;
    private Toolbar mToolbar;
    private TextView mNoReminderView;
    private FloatingActionButton mAddReminderButton;
    private int mTempPost;
    private LinkedHashMap<Integer, Integer> IDmap = new LinkedHashMap<>();
    private ReminderDatabase rb;
    private MultiSelector mMultiSelector = new MultiSelector();
    private AlarmReceiver mAlarmReceiver;
    private FirebaseDatabase firebaseDatabase;     //Entrypoint for our firebase database
    private DatabaseReference reminderDatabaseReference;   //To references specific part of the database
    private ChildEventListener dbChildEventListener;     //No notify the app whenever data changes in firebase database
    private FirebaseAuth firebaseAuth;     //For User authentication
    private FirebaseAuth.AuthStateListener authStateListener;      //Listener which invokes whenever auth state changes
    private ArrayList<ReminderDO> reminderDOArrayList = new ArrayList<>();
    // Multi select items in recycler view
    private ModalMultiSelectorCallback mDeleteMode = new ModalMultiSelectorCallback(mMultiSelector) {

        @Override
        public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
            getMenuInflater().inflate(R.menu.menu_add_reminder, menu);
            return true;
        }

        /** Defines the behaviour of different menu items
         * Overridden Method
         * @param actionMode
         * @param menuItem
         * @return
         */
        @Override
        public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
            switch (menuItem.getItemId()) {

                // On clicking discard reminders
                case R.id.discard_reminder:
                    // Close the context menu
                    actionMode.finish();

                    // Get the reminder id associated with the recycler view item
                    for (int i = IDmap.size(); i >= 0; i--) {
                        if (mMultiSelector.isSelected(i, 0)) {
                            int id = IDmap.get(i);

                            // Get reminder from reminder database using id
                            ReminderDO temp = rb.getReminder(id);
                            // Delete reminder
                            rb.deleteReminder(temp);
                            // Remove reminder from recycler view
                            mAdapter.removeItemSelected(i);
                            // Delete reminder alarm
                            mAlarmReceiver.cancelAlarm(getApplicationContext(), id);
                        }
                    }

                    // Clear selected items in recycler view
                    mMultiSelector.clearSelections();
                    // Recreate the recycler items
                    // This is done to remap the item and reminder ids
                    mAdapter.onDeleteItem(getDefaultItemCount());

                    // Display toast to confirm delete
                    Toast.makeText(getApplicationContext(),
                            "Deleted",
                            Toast.LENGTH_SHORT).show();

                    // To check is there are saved reminders
                    // If there are no reminders display a message asking the user to create reminders
                    List<ReminderDO> mTest = rb.getAllReminders();

                    if (mTest.isEmpty()) {
                        mNoReminderView.setVisibility(View.VISIBLE);
                    } else {
                        mNoReminderView.setVisibility(View.GONE);
                    }

                    return true;

                // On clicking save reminders
                case R.id.save_reminder:
                    // Close the context menu
                    actionMode.finish();
                    // Clear selected items in recycler view
                    mMultiSelector.clearSelections();
                    return true;

                default:
                    break;
            }
            return false;
        }
    };

    /**onCreate() implementation
     * Overridden method. In this method initial life cycle of the app is implemented.
     * If user is not authenticated then user is sent for login/signup, else database instance and main view is initialized
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Routine : check user Authentication
        firebaseDatabase = FirebaseDatabase.getInstance();
        firebaseAuth = FirebaseAuth.getInstance();
        FirebaseUser user = firebaseAuth.getCurrentUser();

        //AuthStateListener: check the user's authentication state. if not signed in the send it for login/signup
        authStateListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    //User is signed in
                    reminderDatabaseReference = firebaseDatabase.getReference().child("reminders").child(user.getUid());
                    Toast.makeText(MainActivity.this, "You're now signed in.\nWelcome to Talking ReminderDO!", Toast.LENGTH_SHORT).show();
                    attachDatabaseReadListener();
                } else {
                    //User is signed out
                    startActivityForResult(
                            AuthUI.getInstance()
                                    .createSignInIntentBuilder()
                                    .setIsSmartLockEnabled(false)
                                    .setAvailableProviders(Arrays.asList(
                                            new AuthUI.IdpConfig.GoogleBuilder().build(),
                                            new AuthUI.IdpConfig.EmailBuilder().build()))
                                    .build(),
                            RC_SIGN_IN);
                }
            }
        };

        // Initialize reminder database
        rb = new ReminderDatabase(getApplicationContext());

        // Initialize views
        mToolbar = findViewById(R.id.toolbar);
        mAddReminderButton = findViewById(R.id.add_reminder);
        reminderListView = findViewById(R.id.reminder_list);
        mNoReminderView = findViewById(R.id.no_reminder_text);

        // To check is there are saved reminders
        // If there are no reminders display a message asking the user to create reminders
        List<ReminderDO> mTest = rb.getAllReminders();

        if (mTest.isEmpty()) {
            mNoReminderView.setVisibility(View.VISIBLE);
        }

        // Create recycler view
        reminderListView.setLayoutManager(getLayoutManager());
        registerForContextMenu(reminderListView);
        mAdapter = new SimpleAdapter();
        mAdapter.setItemCount(getDefaultItemCount());
        reminderListView.setAdapter(mAdapter);

        // Setup toolbar
        setSupportActionBar(mToolbar);
        mToolbar.setTitle(R.string.app_name);

        // On clicking the floating action button
        mAddReminderButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), ReminderAddActivity.class);
                startActivity(intent);
            }
        });

        // Initialize alarm
        mAlarmReceiver = new AlarmReceiver();
    }

    /* Private Helper method
     * This method attaches a listener to the Firebase database reference. The listener listens to any kind of changes in the database.
     *
     */
    private void attachDatabaseReadListener() {
        //TODO Attaching Listener to ChildEventListener
        if (dbChildEventListener == null) {  // Create new Event Listener only when no listener is attached yet
            dbChildEventListener = new ChildEventListener() {
                //Specify what to do when a new entry is added in the database. New entry here means "new Reminder object"
                @Override
                public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                    ReminderDO reminderObject = dataSnapshot.getValue(ReminderDO.class);
                    reminderDOArrayList.add(reminderObject);
                }

                @Override
                public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                }

                @Override
                public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
                }

                @Override
                public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
                }

                @Override
                public void onCancelled(@NonNull DatabaseError databaseError) {
                }
            };
        }
        reminderDatabaseReference.addChildEventListener(dbChildEventListener);
    }

    /* Private helper method
     * this method is called to Detach the database listener in the events of logout or app failure
     */
    private void detachDatabaseReadListener() {
        if (dbChildEventListener != null) {
            reminderDatabaseReference.removeEventListener(dbChildEventListener);
            dbChildEventListener = null;
        }
    }

    /**Define what happens when user closes the app
     * Overridden method
     */
    @Override
    protected void onPause() {
        super.onPause();
        if (authStateListener != null) {
            firebaseAuth.removeAuthStateListener(authStateListener);
        }
        detachDatabaseReadListener();
        //reminderAdapter.clear();
    }

    /** Create context menu for long press actions
     *
     * @param menu
     * @param view
     * @param menuInfo
     */
    @Override
    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
        getMenuInflater().inflate(R.menu.menu_add_reminder, menu);
    }

    /*Private Helper method :
     * Implement behaviour : On clicking a reminder item, user shoud be taken to Reminder Edit Activity
     */
    private void selectReminder(int mClickID) {
        String mStringClickID = Integer.toString(mClickID);

        // Create intent to edit the reminder
        // Put reminder id as extra
        Intent i = new Intent(this, ReminderEditActivity.class);
        i.putExtra(ReminderEditActivity.EXTRA_REMINDER_ID, mStringClickID);
        startActivityForResult(i, 1);
    }

    /** Standard method for handling the outcome of an activity.
     * If user signs in is successful then only take the user to main page. Else force the user to login in first
     *
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mAdapter.setItemCount(getDefaultItemCount());

        if (requestCode == RC_SIGN_IN) {
            //If user signs in is successful then only take the user to main page. Else force the user to login in first
            if (resultCode == RESULT_OK) {
                Toast.makeText(this, "Signed in", Toast.LENGTH_SHORT);
                startActivity(new Intent(MainActivity.this, ReminderAddActivity.class));
            } else if (requestCode == RESULT_CANCELED) {
                Toast.makeText(this, "Sign in cancelled", Toast.LENGTH_SHORT);
                finish();
            }
        }
    }

    /** Define the behaviour of the app in case user comes back after suspending the app.
     * -Recreate recycler view : This is done so that newly created reminders are also displayed

     */
    @Override
    public void onResume() {
        super.onResume();
        firebaseAuth.addAuthStateListener(authStateListener);
        // Get all the reminders : If there are no reminders display a message
        List<ReminderDO> mTest = rb.getAllReminders();

        if (mTest.isEmpty()) {
            mNoReminderView.setVisibility(View.VISIBLE);
        } else {
            mNoReminderView.setVisibility(View.GONE);
        }

        mAdapter.setItemCount(getDefaultItemCount());
    }

    /** Layout manager for recycler view
     *
     * @return LinearLayoutManager instance
     */
    protected RecyclerView.LayoutManager getLayoutManager() {
        return new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
    }

    /** Helper method
     *
     * @return integer 100
     */
    protected int getDefaultItemCount() {
        return 100;
    }

    /**
     * Creating main menu
     *
     * @param Menu object
     * @return boolean
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return true;
    }

    /**Defines behaviours for the menu items.
     *
     * @param item
     * @return Boolean
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.sign_out:
                //sign out
                AuthUI.getInstance().signOut(this);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }


    /**\brief Adapter class (Inner Class) for implementing recycler view
     *
     * This class will handle how to display the reminder data on the main page.
     */
    public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.VerticalItemHolder> {
        private ArrayList<ReminderItem> mItems;

        public SimpleAdapter() {
            mItems = new ArrayList<>();
        }

        public void onDeleteItem(int count) {
            mItems.clear();
            mItems.addAll(generateData(count));
        }

        public void removeItemSelected(int selected) {
            if (mItems.isEmpty()) return;
            mItems.remove(selected);
            notifyItemRemoved(selected);
        }

        /** View holder for recycler view items
         * Overridden method
         *
         * @param container
         * @param viewType
         * @return an object of VerticalItemHolder
         */
        @Override
        public VerticalItemHolder onCreateViewHolder(ViewGroup container, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(container.getContext());
            View root = inflater.inflate(R.layout.recycle_items, container, false);

            return new VerticalItemHolder(root, this);
        }

        /**Bind a reminder item to a position in the main view
         * Overridden method
         *
         * @param itemHolder
         * @param position
         */
        @Override
        public void onBindViewHolder(VerticalItemHolder itemHolder, int position) {
            ReminderItem item = mItems.get(position);
            itemHolder.setReminderTitle(item.mTitle);
            itemHolder.setReminderDateTime(item.mDateTime);
            itemHolder.setReminderRepeatInfo(item.mRepeat, item.mRepeatNo, item.mRepeatType);
            itemHolder.setActiveImage(item.mActive);
        }

        /**Retuns the number of reminders in the database
         *
         * @return
         */
        @Override
        public int getItemCount() {
            return mItems.size();
        }


        public void setItemCount(int count) {
            mItems.clear();
            mItems.addAll(generateData(count));
            notifyDataSetChanged();
        }

        /** Prepare data for each reminder item to facilitate proper displaying in the view.
         *  this Helper method required because all the data in database are not directly representable.
         *
         * @param count
         * @return
         */
        public List<ReminderItem> generateData(int count) {
            ArrayList<SimpleAdapter.ReminderItem> items = new ArrayList<>();

            // Get all reminderDOS from the database
            List<ReminderDO> reminderDOS = rb.getAllReminders();

            // Initialize lists
            List<String> Titles = new ArrayList<>();
            List<String> Repeats = new ArrayList<>();
            List<String> RepeatNos = new ArrayList<>();
            List<String> RepeatTypes = new ArrayList<>();
            List<String> Actives = new ArrayList<>();
            List<String> DateAndTime = new ArrayList<>();
            List<Integer> IDList = new ArrayList<>();
            List<DateTimeSorter> DateTimeSortList = new ArrayList<>();

            // Add details of all reminderDOS in their respective lists
            for (ReminderDO reminderDO : reminderDOS) {
                Titles.add(reminderDO.getTitle());
                DateAndTime.add(reminderDO.getDate() + " " + reminderDO.getTime());
                Repeats.add(reminderDO.getRepeat());
                RepeatNos.add(reminderDO.getRepeatNo());
                RepeatTypes.add(reminderDO.getRepeatType());
                Actives.add(reminderDO.getActive());
                IDList.add(reminderDO.getID());
            }

            int key = 0;

            // Add date and time as DateTimeSorter objects
            for (int k = 0; k < Titles.size(); k++) {
                DateTimeSortList.add(new DateTimeSorter(key, DateAndTime.get(k)));
                key++;
            }

            // Sort items according to date and time in ascending order
            Collections.sort(DateTimeSortList, new DateTimeComparator());

            int k = 0;

            // Plug in the data to each recycler view item
            for (DateTimeSorter item : DateTimeSortList) {
                int i = item.getIndex();

                items.add(new SimpleAdapter.ReminderItem(Titles.get(i), DateAndTime.get(i), Repeats.get(i),
                        RepeatNos.get(i), RepeatTypes.get(i), Actives.get(i)));
                IDmap.put(k, IDList.get(i));
                k++;
            }
            return items;
        }

        /** Data-Object class for recycler view items
         *  This class is slightly different form ReminderDO class. Very specific to the requirement of the recyler view
         */
        public class ReminderItem {
            public String mTitle;
            public String mDateTime;
            public String mRepeat;
            public String mRepeatNo;
            public String mRepeatType;
            public String mActive;

            /**Constructor
             *
             * @param Title
             * @param DateTime
             * @param Repeat
             * @param RepeatNo  number of repeatition
             * @param RepeatType
             * @param Active
             */
            public ReminderItem(String Title, String DateTime, String Repeat, String RepeatNo, String RepeatType, String Active) {
                this.mTitle = Title;
                this.mDateTime = DateTime;
                this.mRepeat = Repeat;
                this.mRepeatNo = RepeatNo;
                this.mRepeatType = RepeatType;
                this.mActive = Active;
            }
        }

        /** Inner class to compare date and time so that items can be sorted in ascending/decending order of date and time
         *
         */
        public class DateTimeComparator implements Comparator {
            DateFormat f = new SimpleDateFormat("dd/mm/yyyy hh:mm");

            public int compare(Object a, Object b) {
                String o1 = ((DateTimeSorter) a).getDateTime();
                String o2 = ((DateTimeSorter) b).getDateTime();

                try {
                    return f.parse(o1).compareTo(f.parse(o2));
                } catch (ParseException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        }

        /** UI and data class (Adapter) for recycler view items
         *
         */
        public class VerticalItemHolder extends SwappingHolder
                implements View.OnClickListener, View.OnLongClickListener {
            private TextView mTitleText, mDateAndTimeText, mRepeatInfoText;
            private ImageView mActiveImage, mThumbnailImage;
            private ColorGenerator mColorGenerator = ColorGenerator.DEFAULT;
            private TextDrawable mDrawableBuilder;
            private SimpleAdapter mAdapter;

            /**
             * Sets up recycler item selector in the context.
             * On long clicks the items should be selected so that user can take actions (like delete) on those items
             *
             * @param itemView
             * @param adapter
             */
            public VerticalItemHolder(View itemView, SimpleAdapter adapter) {
                super(itemView, mMultiSelector);
                itemView.setOnClickListener(this);
                itemView.setOnLongClickListener(this); //Set for long clicks
                itemView.setLongClickable(true);

                // Initialize adapter for the items
                mAdapter = adapter;

                // Initialize views
                mTitleText = itemView.findViewById(R.id.recycle_title);
                mDateAndTimeText = itemView.findViewById(R.id.recycle_date_time);
                mRepeatInfoText = itemView.findViewById(R.id.recycle_repeat_info);
                mActiveImage = itemView.findViewById(R.id.active_image);
                mThumbnailImage = itemView.findViewById(R.id.thumbnail_image);
            }

            /** On clicking a reminder item
             *
             * @param view
             */
            @Override
            public void onClick(View view) {
                if (!mMultiSelector.tapSelection(this)) {
                    mTempPost = reminderListView.getChildAdapterPosition(view);

                    int mReminderClickID = IDmap.get(mTempPost);
                    selectReminder(mReminderClickID);

                } else if (mMultiSelector.getSelectedPositions().isEmpty()) {
                    mAdapter.setItemCount(getDefaultItemCount());
                }
            }

            /** On long press enter action mode with context menu
             *
             * @param view
             * @return
             */
            @Override
            public boolean onLongClick(View view) {
                AppCompatActivity activity = MainActivity.this;
                activity.startSupportActionMode(mDeleteMode);
                mMultiSelector.setSelected(this, true);
                return true;
            }

            /** Set reminder title view
             *
             * @param reminderTitle
             */
            public void setReminderTitle(String reminderTitle) {
                mTitleText.setText(reminderTitle);
                String letter = "A";

                if (reminderTitle != null && !reminderTitle.isEmpty()) {
                    letter = reminderTitle.substring(0, 1);
                }

                int color = mColorGenerator.getRandomColor();

                // Create a circular icon consisting of  a random background colour and first letter of title
                mDrawableBuilder = TextDrawable.builder()
                        .buildRound(letter, color);
                mThumbnailImage.setImageDrawable(mDrawableBuilder);
            }

            /** Set date and time views
             *
             * @param datetime
             */
            public void setReminderDateTime(String datetime) {
                mDateAndTimeText.setText(datetime);
            }

            /** Set repeat views
             *
             * @param repeat
             * @param repeatNo
             * @param repeatType
             */
            public void setReminderRepeatInfo(String repeat, String repeatNo, String repeatType) {
                if (repeat.equals("true")) {
                    mRepeatInfoText.setText("Every " + repeatNo + " " + repeatType + "(s)");
                } else if (repeat.equals("false")) {
                    mRepeatInfoText.setText("Repeat Off");
                }
            }

            /** Set active image as on or off
             * This method is apparently not consistent because on/off behaviour doesn't always work
             * @param active
             */
            public void setActiveImage(String active) {
                if (active.equals("true")) {
                    mActiveImage.setImageResource(R.drawable.ic_notifications_active);
                } else if (active.equals("false")) {
                    mActiveImage.setImageResource(R.drawable.ic_notifications_off);
                }
            }
        }
    }
}
