package com.reindeercrafts.notificationpeek.peek;

import android.app.Activity;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.service.notification.StatusBarNotification;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.reindeercrafts.notificationpeek.NotificationHub;
import com.reindeercrafts.notificationpeek.R;
import com.reindeercrafts.notificationpeek.settings.PreferenceKeys;
import com.reindeercrafts.notificationpeek.settings.appearance.WallpaperFactory;
import com.reindeercrafts.notificationpeek.utils.NotificationPeekViewUtils;

import java.util.Date;

/**
 * This class is used to display the Notification Peek layout. The original implementation
 * uses Window and directly added layout to it, but it seems impossible to do so externally.
 * <p/>
 * This class also controls waking up the device and removing Peek layout from its parent.
 */
public class NotificationPeekActivity extends Activity {

    private static final String TAG = NotificationPeekActivity.class.getSimpleName();

    private static final long LOCK_DELAY = 200; // 200 ms
    private TextView mClockTextView;
    private NotificationPeekReceiver mReceiver;

    private RelativeLayout mPeekView;
    private NotificationLayout mNotificationLayout;
    private GridLayout mNotificationsContainer;
    private ViewGroup mNotificationView;
    private ImageView mNotificationIcon;
    private TextView mNotificationText;

    private NotificationPeek mPeek;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
                        WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
        );

        if (WallpaperFactory.isLiveWallpaperUsed(this) &&
                WallpaperFactory.isWallpaperThemeSelected(this)) {
            setTheme(R.style.AppTheme_Peek_Wallpaper);
        } else {
            setTheme(R.style.AppTheme_Peek);
        }

        super.onCreate(savedInstanceState);

        mPeekView = NotificationPeek.getPeekView();

        mNotificationsContainer = (GridLayout) mPeekView.findViewById(R.id.notification_container);
        mNotificationView = (ViewGroup) mPeekView.findViewById(R.id.notification_view);

        setContentView(mPeekView);

        boolean showClock = PreferenceManager.getDefaultSharedPreferences(this)
                .getBoolean(PreferenceKeys.PREF_CLOCK, true);

        if (showClock) {
            mClockTextView = (TextView) PeekLayoutFactory
                    .createPeekLayout(this, PeekLayoutFactory.LAYOUT_TYPE_CLOCK, null);
            mPeekView.addView(mClockTextView);
            mClockTextView.setText(getCurrentTimeText());
        }

        mPeekView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE |
                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN |
                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        );

        // Initialize broadcast receiver.
        initReceiver();

        // Retrieve NotificationPeek object.
        mNotificationLayout = (NotificationLayout) mPeekView.findViewById(R.id.notification_layout);
        mPeek = mNotificationLayout.getNotificationPeek();

        // Setup OnClickListener.
        mNotificationIcon = (ImageView) mPeekView.findViewById(R.id.notification_icon);

        // Notification snippet TextView.
        mNotificationText = (TextView) mPeekView.findViewById(R.id.notification_text);

    }

    private void initReceiver() {

        mReceiver = new NotificationPeekReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(NotificationPeekReceiver.ACTION_FINISH_PEEK);
        filter.addAction(NotificationPeekReceiver.ACTION_DIMISS_NOTIFICATION);
        filter.addAction(NotificationPeekReceiver.ACTION_UPDATE_NOTIFICATION_ICONS);
        filter.addAction(NotificationPeekReceiver.ACTION_SHOW_CONTENT);
        filter.addAction(NotificationPeekReceiver.ACTION_HIDE_CONTENT);
        // Add time tick intent only when the clock is shown.
        if (mClockTextView != null) {
            filter.addAction(Intent.ACTION_TIME_TICK);
        }

        registerReceiver(mReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Remove mPeekView from its parent so that it can be reused.
        ViewGroup parent = (ViewGroup) mPeekView.getParent();
        parent.removeView(mPeekView);

        try {
            // Remove Clock only when it is displayed.
            if (mClockTextView != null) {
                mPeekView.removeView(mClockTextView);
            }
            unregisterReceiver(mReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
        mPeekView = null;
    }

    /**
     * Get formatted time String (Follows system setting).
     *
     * @return Formatted time String.
     */
    private String getCurrentTimeText() {
        return android.text.format.DateFormat.getTimeFormat(this).format(new Date());
    }

    /**
     * Update notification views upon each swipe, moving the next latest notification to 'Current
     * Notification' spot, and remove it from the small icon container.
     *
     * @param description Description of the StatusBarNotification we just swiped.
     * @return True if the update is successful, i.e there is more than one unread notification.
     * False if the notification we just swipe away is the last unread notification.
     */
    private boolean updateNotification(String description) {

        int currentNotificationIdex =
                getCurrentNotificationIndex(mNotificationsContainer, description);

        // Remove the current notification from container.
        mNotificationsContainer.removeViewAt(currentNotificationIdex);

        int nextNotificationIndex = mNotificationsContainer.getChildCount() - 1;

        // We have more than one unread notification.
        if (nextNotificationIndex >= 0) {
            StatusBarNotification nextNotification = (StatusBarNotification) mNotificationsContainer
                    .getChildAt(nextNotificationIndex).getTag();

            if (nextNotification.getNotification().largeIcon != null) {
                mNotificationIcon.setImageDrawable(NotificationPeekViewUtils
                        .getRoundedShape(getResources(),
                                nextNotification.getNotification().largeIcon));
            } else {
                mNotificationIcon.setImageDrawable(
                        NotificationPeekViewUtils.getIconFromResource(this, nextNotification));
            }

            mNotificationText.setText(
                    NotificationPeekViewUtils.getNotificationDisplayText(this, nextNotification));

            // Animate back icon and text.
            mNotificationView.setTranslationX(0);
            mNotificationView.animate().alpha(1f).start();

            // Set new tag.
            mNotificationView.setTag(nextNotification);
            mPeek.getNotificationHub().setCurrentNotification(nextNotification);

            final PendingIntent contentIntent = nextNotification.getNotification().contentIntent;
            if (contentIntent != null) {
                NotificationClicker mNotificationClicker =
                        new NotificationClicker(this, contentIntent, mPeek);
                mNotificationIcon.setOnClickListener(mNotificationClicker);
            } else {
                mNotificationIcon.setOnClickListener(null);
            }

            if (nextNotificationIndex == 0) {
                // As we already moved the next notification to 'Current Notification' spot, we need
                // to hide it too if there is only one unread notification left.
                mNotificationsContainer.getChildAt(nextNotificationIndex).setVisibility(View.GONE);
            } else {
                // Otherwise, highlight that icon.
                mNotificationsContainer.getChildAt(nextNotificationIndex).setAlpha(1);
            }
            // Recover TextView alpha, because we will still have notification(s) to show.
            mPeek.updateNotificationTextAlpha(1);

            return true;
        }

        // The only unread notification is swiped away.
        return false;
    }


    private int getCurrentNotificationIndex(ViewGroup container, String description) {
        for (int i = 0; i < container.getChildCount(); i++) {
            StatusBarNotification n = (StatusBarNotification) container.getChildAt(i).getTag();
            if (NotificationHelper.getContentDescription(n).equals(description)) {
                return i;
            }
        }

        return -1;
    }

    private void restoreFirstIconVisibility() {
        View firstIcon = mNotificationsContainer.getChildAt(0);
        if (firstIcon.getVisibility() == View.GONE) {
            // The first icon is hidden before in response to the swipe gesture,
            firstIcon.setVisibility(View.VISIBLE);
        }

        // Highlight the first notification if it is currently selected.
        StatusBarNotification largeIconNotification =
                (StatusBarNotification) mNotificationView.getTag();
        StatusBarNotification firstIconNotification = (StatusBarNotification) firstIcon.getTag();
        if (largeIconNotification.getPackageName().equals(firstIconNotification.getPackageName())) {
            firstIcon.setAlpha(1f);
        }

    }

    /**
     * Check whether a notification with the same package name as the new notification is
     * shown in the icon container.
     *
     * @param hub NotificationHub instance.
     * @return Index of the icon ImageView in its parent. -1 if not found.
     */
    private int getOldIconViewIndex(NotificationHub hub) {
        for (int i = 0; i < mNotificationsContainer.getChildCount(); i++) {
            View child = mNotificationsContainer.getChildAt(i);

            if (child.getTag() == null) {
                continue;
            }

            StatusBarNotification n = (StatusBarNotification) child.getTag();

            if (n.getPackageName().equals(hub.getCurrentNotification().getPackageName())) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Update small notification icons when there is new notification coming, and the
     * Activity is in foreground.
     */
    private void updateNotificationIcons() {

        if (mNotificationsContainer.getVisibility() != View.VISIBLE) {
            mNotificationsContainer.setVisibility(View.VISIBLE);
        }

        NotificationHub notificationHub = NotificationHub.getInstance();

        int iconSize = getResources().getDimensionPixelSize(R.dimen.small_notification_icon_size);
        int padding = getResources().getDimensionPixelSize(R.dimen.small_notification_icon_padding);

        final StatusBarNotification n = notificationHub.getCurrentNotification();
        ImageView icon = new ImageView(this);
        icon.setAlpha(NotificationPeek.ICON_LOW_OPACITY);

        icon.setPadding(padding, 0, padding, 0);
        icon.setImageDrawable(NotificationPeekViewUtils.getIconFromResource(this, n));
        icon.setTag(n);

        restoreFirstIconVisibility();

        int oldIndex = getOldIconViewIndex(notificationHub);

        if (oldIndex >= 0) {
            mNotificationsContainer.removeViewAt(oldIndex);
        }

        mNotificationsContainer.addView(icon);
        LinearLayout.LayoutParams linearLayoutParams =
                new LinearLayout.LayoutParams(iconSize, iconSize);

        // Wrap LayoutParams to GridLayout.LayoutParams.
        GridLayout.LayoutParams gridLayoutParams = new GridLayout.LayoutParams(linearLayoutParams);
        icon.setLayoutParams(gridLayoutParams);
    }

    /**
     * Animate the small TextView below the icon and the clock TextView and hide them when
     * the notification content is displayed.
     */
    private void hidePeekComponents() {
        mNotificationText.animate().alpha(0f).setInterpolator(new DecelerateInterpolator()).start();
        if (mClockTextView != null) {
            mClockTextView.animate().alpha(0f).setInterpolator(new DecelerateInterpolator())
                    .start();
        }
    }

    /**
     * Bring the small TextView and the clock TextView back when the notification content
     * is removed.
     */
    private void showPeekComponents() {
        mNotificationText.animate().alpha(1f).setInterpolator(new AccelerateInterpolator()).start();
        if (mClockTextView != null) {
            mClockTextView.animate().alpha(1f).setInterpolator(new AccelerateInterpolator())
                    .start();
        }
    }

    /**
     * Release partial wake lock from NotificationPeek object and
     * lock screen.
     */
    private void lockScreen() {

        // Lock screen by a fixed delay, otherwise sometimes the screen will turn on again
        // showing the lock screen UI.
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                // Release partial wake lock.
                mPeek.dismissNotification();

                DevicePolicyManager devicePolicyManager =
                        (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
                devicePolicyManager.lockNow();
            }
        }, LOCK_DELAY);


    }


    public class NotificationPeekReceiver extends BroadcastReceiver {

        // Action for dismissing notification peek view.
        public static final String ACTION_DIMISS_NOTIFICATION =
                "NotificationPeek.dismiss_notification";

        // Action for updating notification icons.
        public static final String ACTION_UPDATE_NOTIFICATION_ICONS =
                "NotificationPeek.update_notification";

        // Action for finishing this activity.
        public static final String ACTION_FINISH_PEEK = "NotificationPeek.finish_peek";

        public static final String ACTION_SHOW_CONTENT = "NotificationPeek.show_content";

        public static final String ACTION_HIDE_CONTENT = "NotificationPeek.hide_content";

        public static final String EXTRA_NOTIFICATION_DESCRIPTION =
                "NotificationPeek.extra_status_bar_notification_description";


        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ACTION_FINISH_PEEK)) {
                finish();
            } else if (intent.getAction().equals(ACTION_DIMISS_NOTIFICATION)) {
                String description = intent.getStringExtra(EXTRA_NOTIFICATION_DESCRIPTION);
                if (!updateNotification(description)) {
                    lockScreen();
                }
            } else if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
                mClockTextView.setText(getCurrentTimeText());
            } else if (intent.getAction().equals(ACTION_UPDATE_NOTIFICATION_ICONS)) {
                // Update notification icons according to actions.
                updateNotificationIcons();
            } else if (intent.getAction().equals(ACTION_SHOW_CONTENT)) {
                // Display current notification's content.
                hidePeekComponents();
            } else if (intent.getAction().equals(ACTION_HIDE_CONTENT)) {
                // Hide notification content view.
                showPeekComponents();
            }
        }
    }


}