/*
 * Copyright (C) 2019 Peter Gregus for GravityBox Project (C3C076@xda)
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ceco.r.gravitybox;

import java.io.File;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.provider.CallLog;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.ceco.r.gravitybox.managers.SysUiConfigChangeMonitor;
import com.ceco.r.gravitybox.managers.SysUiKeyguardStateMonitor;
import com.ceco.r.gravitybox.managers.SysUiNotificationDataMonitor;
import com.ceco.r.gravitybox.managers.SysUiManagers;
import com.ceco.r.gravitybox.preference.AppPickerPreference;
import com.ceco.r.gravitybox.shortcuts.ShortcutActivity;

import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

public class LockscreenAppBar implements SysUiKeyguardStateMonitor.Listener,
                                         SysUiNotificationDataMonitor.Listener,
                                         SysUiConfigChangeMonitor.ConfigChangeListener {
    private static final String TAG = "GB:LockscreenAppBar";
    private static final boolean DEBUG = false;

    private static final float DEFAULT_SIZE_DP = 40f;

    private static void log(String message) {
        XposedBridge.log(TAG + ": " + message);
    }

    private Context mContext;
    private Context mGbContext;
    private ViewGroup mContainer;
    private Object mNotifPanel;
    private PackageManager mPm;
    private List<AppInfo> mAppSlots;
    private ViewGroup mRootView;
    private XSharedPreferences mPrefs;
    private boolean mSafeLaunchEnabled;
    private boolean mShowBadges;
    private AppInfo mPendingAction;
    private Handler mHandler;
    private SysUiKeyguardStateMonitor mKgMonitor;
    private SysUiNotificationDataMonitor mNdMonitor;
    private boolean mIsActive;
    private boolean mIsInteractive = true;
    private int mScale;

    public LockscreenAppBar(Context ctx, Context gbCtx, ViewGroup container,
            Object notifPanel, XSharedPreferences prefs) {
        mContext = ctx;
        mGbContext = gbCtx;
        mContainer = container;
        mNotifPanel = notifPanel;
        mPrefs = prefs;
        mPm = mContext.getPackageManager();
        mHandler = new Handler();
        mSafeLaunchEnabled = prefs.getBoolean(
                GravityBoxSettings.PREF_KEY_LOCKSCREEN_SHORTCUT_SAFE_LAUNCH, false);
        mShowBadges = prefs.getBoolean(
                GravityBoxSettings.PREF_KEY_LOCKSCREEN_SHORTCUT_SHOW_BADGES, false);
        mScale = prefs.getInt(GravityBoxSettings.PREF_KEY_LOCKSCREEN_SHORTCUT_SCALE, 0);

        mNdMonitor = SysUiManagers.NotifDataMonitor;
        if (mNdMonitor != null) {
            mNdMonitor.registerListener(this);
        }

        LayoutInflater inflater = LayoutInflater.from(mGbContext);
        mRootView = (ViewGroup) inflater.inflate(R.layout.lockscreen_app_bar, mContainer, false);
        mContainer.addView(mRootView);
        mContainer.setClipChildren(false);
        if (mContainer.getParent() instanceof ViewGroup) {
            ((ViewGroup)mContainer.getParent()).setClipChildren(false);
        }
        updateScale();

        mAppSlots = new ArrayList<>(7);
        mAppSlots.add(new AppInfo(R.id.quickapp1));
        mAppSlots.add(new AppInfo(R.id.quickapp2));
        mAppSlots.add(new AppInfo(R.id.quickapp3));
        mAppSlots.add(new AppInfo(R.id.quickapp4));
        mAppSlots.add(new AppInfo(R.id.quickapp5));
        mAppSlots.add(new AppInfo(R.id.quickapp6));
        mAppSlots.add(new AppInfo(R.id.quickapp7));

        mKgMonitor = SysUiManagers.KeyguardMonitor;
        mKgMonitor.registerListener(this);
    }

    @Override
    public void onDensityDpiChanged(Configuration config) {
        try {
            mGbContext = Utils.getGbContext(mContext, config);
        } catch (Throwable t) {
            GravityBox.log(TAG, t);
        }
        updateScale();
    }

    private void updateScale() {
        float scaledSizeDp = DEFAULT_SIZE_DP * (1 + (float)mScale/100f);
        int scaledSizePx = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                scaledSizeDp, mContext.getResources().getDisplayMetrics()));
        int childCount = mRootView.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View v = mRootView.getChildAt(i);
            ViewGroup.LayoutParams lp = v.getLayoutParams();
            lp.width = lp.height = scaledSizePx;
            v.setLayoutParams(lp);
        }
    }

    public void initAppSlots() {
        if (DEBUG) log("Initializing app slots");

        for (int i = 0; i < GravityBoxSettings.PREF_KEY_LOCKSCREEN_SHORTCUT.size(); i++) {
            updateAppSlot(i, mPrefs.getString(GravityBoxSettings.PREF_KEY_LOCKSCREEN_SHORTCUT.get(i),
                    null), false);
        }
        updateRootViewVisibility();
    }

    public void setSafeLaunchEnabled(boolean enabled) {
        mSafeLaunchEnabled = enabled;
    }

    public void setShowBadges(boolean showBadges) {
        mShowBadges = showBadges;
        onNotificationDataChanged(null);
    }

    public void setScale(int scale) {
        mScale = scale;
        updateScale();
    }

    public void updateAppSlot(int slot, String value) {
        updateAppSlot(slot, value, true);
    }

    private void updateAppSlot(int slot, String value, boolean updateRootViewVisibility) {
        mAppSlots.get(slot).initAppInfo(value);
        if (updateRootViewVisibility) {
            updateRootViewVisibility();
        }
    }

    private void updateRootViewVisibility() {
        boolean atLeastOneVisible = false;
        int childCount = mRootView.getChildCount();
        for (int i = 0; i < childCount; i++) {
            if (mRootView.getChildAt(i).getVisibility() == View.VISIBLE) {
                atLeastOneVisible = true;
                break;
            }
        }
        mIsActive = atLeastOneVisible;
        animateVisibility(mIsActive && mIsInteractive);
    }

    private void animateVisibility(final boolean visible) {
        if (!visible) {
            mRootView.setVisibility(View.GONE);
            mRootView.setScaleX(0f);
            mRootView.setScaleY(0f);
        } else {
            mRootView.setVisibility(View.VISIBLE);
            mRootView.animate()
                    .scaleX(1f)
                    .scaleY(1f)
                    .setDuration(300)
                    .start();
        }
    }

    @Override
    public void onKeyguardStateChanged() {
        for (AppInfo ai : mAppSlots) {
            if (ai.isUnsafeAction()) {
                ai.setVisible(!mKgMonitor.isLocked());
            }
            if (ModTelecom.PACKAGE_NAME.equals(ai.getPackageName())) {
                ai.updateIcon();
            }
        }
    }

    @Override
    public void onScreenStateChanged(boolean interactive) {
        mIsInteractive = interactive;
        animateVisibility(mIsActive && mIsInteractive);
    }

    @Override
    public void onNotificationDataChanged(final StatusBarNotification sbn) {
        for (AppInfo ai : mAppSlots) {
            if (ai.getPackageName() == null)
                continue;

            if (sbn == null || sbn.getPackageName() == null ||
                    sbn.getPackageName().equals(ai.getPackageName())) {
                ai.updateIcon();
            }
        }
    }

    private void startActivity(Intent intent) {
        // if intent is a GB action of broadcast type, handle it directly here
        if (ShortcutActivity.isGbBroadcastShortcut(intent)) {
            boolean isLaunchBlocked = mKgMonitor.isShowing() && mKgMonitor.isLocked() &&
                    !ShortcutActivity.isActionSafe(intent.getStringExtra(
                            ShortcutActivity.EXTRA_ACTION));
            if (DEBUG) log("isLaunchBlocked: " + isLaunchBlocked);

            if (!isLaunchBlocked) {
                Intent newIntent = new Intent(intent.getStringExtra(ShortcutActivity.EXTRA_ACTION));
                newIntent.putExtras(intent);
                mContext.sendBroadcast(newIntent);
            }
        // otherwise start activity dismissing keyguard
        } else {
            try {
                Object statusBar = XposedHelpers.getObjectField(mNotifPanel, "mStatusBar");
                XposedHelpers.callMethod(statusBar, "postStartActivityDismissingKeyguard", intent, 0);
            } catch (Throwable t) {
                GravityBox.log(TAG, t);
            }
        }
    }

    private int getMissedCallCount() {
        try {
            String[] selection = { CallLog.Calls.TYPE };
            String where = CallLog.Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE +
                    " AND " + CallLog.Calls.NEW + "=1";
            Cursor c = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, selection, where, null, null);
            int count = c.getCount();
            c.close();
            return count;
        } catch (Throwable t) {
            GravityBox.log(TAG, t);
            return 0;
        }
    }

    private Runnable pendingActionExpiredRunnable = new Runnable() {
        @Override
        public void run() {
            if (mPendingAction != null) {
                mPendingAction.zoomOut();
                mPendingAction = null;
            }
        }
    };

    private final class AppInfo implements View.OnClickListener {
        private Intent mIntent;
        private Resources mResources;
        private Resources mGbResources;
        private ImageView mView;
        private Drawable mIcon;

        public AppInfo(int resId) {
            mResources = mContext.getResources();
            mGbResources = mGbContext.getResources();
            mView = mRootView.findViewById(resId);
            mView.setVisibility(View.GONE);
            mView.setOnClickListener(this);
        }

        private void reset() {
            mIntent = null;
            mIcon = null;
            mView.setImageDrawable(null);
            mView.setVisibility(View.GONE);
        }

        public void initAppInfo(String value) {
            reset();
            if (value == null) {
                return;
            }

            try {
                mIntent = Intent.parseUri(value, 0);
                if (!mIntent.hasExtra("mode")) {
                    reset();
                    return;
                }
                final int mode = mIntent.getIntExtra("mode", AppPickerPreference.MODE_APP);

                final int iconResId = mIntent.getStringExtra("iconResName") != null ?
                        mGbResources.getIdentifier(mIntent.getStringExtra("iconResName"),
                        "drawable", mGbContext.getPackageName()) : 0;
                if (iconResId != 0) {
                    mIcon = mGbContext.getDrawable(iconResId);
                } else {
                    final String appIconPath = mIntent.getStringExtra("icon");
                    if (appIconPath != null) {
                        File f = new File(appIconPath);
                        if (f.exists() && f.canRead()) {
                            mIcon = Drawable.createFromPath(f.getAbsolutePath());
                        }
                    }
                }

                if (mIcon == null) {
                    if (mode == AppPickerPreference.MODE_APP) {
                        ActivityInfo ai = mPm.getActivityInfo(mIntent.getComponent(), 0);
                        mIcon = ai.loadIcon(mPm);
                    } else {
                        mIcon = mContext.getDrawable(android.R.drawable.ic_menu_help);
                    }
                }

                updateIcon();
                mView.setVisibility(View.VISIBLE);
                if (DEBUG) log("AppInfo initialized for: " + mIntent);
            } catch (NameNotFoundException e) {
                GravityBox.log(TAG, "App not found: " + mIntent);
                reset();
            } catch (Exception e) {
                GravityBox.log(TAG, "Unexpected error: ", e);
                reset();
            }
        }

        private String getPackageName() {
            if (mIntent != null && mIntent.getComponent() != null &&
                    mIntent.getComponent().getPackageName() != null) {
                final String pkgName = mIntent.getComponent().getPackageName();
                return (pkgName.equals(Utils.getDefaultDialerPackageName(mContext)) ?
                        ModTelecom.PACKAGE_NAME : pkgName);
            }
            return null;
        }

        public void updateIcon() {
            if (mIcon == null || mIntent == null) return;

            Drawable d = mIcon;

            final int mode = mIntent.getIntExtra("mode", AppPickerPreference.MODE_APP);
            if (mShowBadges && mode == AppPickerPreference.MODE_APP) {
                int count = getNotifCount();
                if (count > 0) {
                    d = createBadgeDrawable(d, count);
                }
            }

            mView.setImageDrawable(d);
        }

        private int getNotifCount() {
            final String pkgName = getPackageName();
            if (ModTelecom.PACKAGE_NAME.equals(pkgName)) {
                return getMissedCallCount();
            } else if (mNdMonitor != null) {
                return mNdMonitor.getNotifCountFor(pkgName);
            } else {
                return 0;
            }
        }

        private Drawable createBadgeDrawable(Drawable d, int count) {
            if (d == null) return null;

            NumberFormat f = NumberFormat.getIntegerInstance();
            String countStr = count > 99 ? "99+" : f.format(count);

            Bitmap b = BitmapUtils.drawableToBitmap(d);
            b = b.copy(Bitmap.Config.ARGB_8888, true);
            Canvas c = new Canvas(b);

            Paint p = new Paint();
            p.setTextAlign(Paint.Align.CENTER);
            p.setColor(Color.WHITE);
            p.setAntiAlias(true);
            p.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10,
                    mResources.getDisplayMetrics()));

            Drawable bg = mGbContext.getDrawable(R.drawable.ic_notification_overlay);

            final int w = b.getWidth();
            final int h = b.getHeight();
            final Rect r = new Rect();
            p.getTextBounds(countStr, 0, countStr.length(), r);
            final int tw = r.right - r.left;
            final int th = r.bottom - r.top;
            bg.getPadding(r);
            int dw = r.left + tw + r.right;
            if (dw < bg.getMinimumWidth()) {
                dw = bg.getMinimumWidth();
            }
            int x = w-r.right-((dw-r.right-r.left)/2);
            int dh = r.top + th + r.bottom;
            if (dh < bg.getMinimumHeight()) {
                dh = bg.getMinimumHeight();
            }
            if (dw < dh) dw = dh;
            int y = h-r.bottom-((dh-r.top-th-r.bottom)/2);
            bg.setBounds(w-dw, h-dh, w, h);

            bg.draw(c);
            c.drawText(countStr, x, y, p);

            return new BitmapDrawable(mResources, b);
        }

        public boolean isUnsafeAction() {
            return (mIntent != null &&
                    !ShortcutActivity.isActionSafe(mIntent.getStringExtra(
                            ShortcutActivity.EXTRA_ACTION)));
        }

        public void setVisible(boolean visible) {
            if (mView != null) {
                mView.setVisibility(visible ? View.VISIBLE : View.GONE);
            }
        }

        public void zoomIn() {
            if (mView != null) {
                mView.animate()
                    .scaleX(1.2f)
                    .scaleY(1.2f)
                    .setDuration(100)
                    .start();
            }
        }

        public void zoomOut() {
            if (mView != null) {
                mView.animate()
                    .scaleX(1f)
                    .scaleY(1f)
                    .setDuration(100)
                    .start();
            }
        }

        @Override
        public void onClick(View v) {
            if (mSafeLaunchEnabled) {
                mHandler.removeCallbacks(pendingActionExpiredRunnable);
    
                if (mPendingAction == this) {
                    pendingActionExpiredRunnable.run();
                    if (mIntent != null) {
                        startActivity(mIntent);
                    }
                } else {
                    pendingActionExpiredRunnable.run();
                    mPendingAction = this;
                    zoomIn();
                    mHandler.postDelayed(pendingActionExpiredRunnable, 1300);
                }
            } else {
                if (mIntent != null) {
                    startActivity(mIntent);
                }
            }
        }
    }
}
