/*
 * Copyright (C) 2015 Zemin Liu
 *
 * 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 zemin.notification;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView;

/**
 * A floating notification window holds a notification root view {@link NotificationRootView},
 * where both view {@link NotificationView} and board {@link NotificationBoard} are sitting on.
 */
public class NotificationGlobal extends NotificationHandler {

    public static final String SIMPLE_NAME = "Global";
    public static boolean DBG;

    /**
     * Implementaiton of {@link NotificationViewCallback} for floating {@link NotificationView}.
     */
    public static class ViewCallback extends NotificationViewCallback {

        @Override
        public int getContentViewDefaultLayoutId(NotificationView view) {
            return R.layout.notification_full;
        }

        @Override
        public void onViewSetup(NotificationView view) {
            view.setContentMargin(0, 0, 0, 0);
            view.setDefaultBackgroundColor(0xff000000);
            view.setDefaultBackgroundAlpha(0x7f);
        }

        @Override
        public void onContentViewChanged(NotificationView view, View contentView, int layoutId) {
            super.onContentViewChanged(view, contentView, layoutId);

            ChildViewManager mgr = view.getChildViewManager();
            mgr.setTextColor(TITLE, 0xffffffff);
            mgr.setTextColor(TEXT, 0xffffffff);
            mgr.setTextColor(WHEN, 0xffffffff);
        }
    }

    private NotificationView mView;
    private NotificationBoard mBoard;
    private NotificationWindow mWindow;

    /* package */ NotificationGlobal(Context context, Looper looper) {
        super(context, NotificationDelegater.GLOBAL, looper);

        mWindow = new NotificationWindow(context);
    }

    /**
     * Get notification view.
     *
     * @return NotificationView
     */
    public NotificationView getView() {
        return mView;
    }

    /**
     * Get notification board.
     *
     * @return NotificationBoard
     */
    public NotificationBoard getBoard() {
        return mBoard;
    }

    /**
     * Enable/disable notification view.
     *
     * @param enable
     */
    public void setViewEnabled(boolean enable) {
        final NotificationRootView root = mWindow.mRoot;
        root.setViewEnabled(enable);
        if (enable && mView == null) {
            mView = root.getView();
            mView.initialize(this);
            mView.addStateListener(new ViewStateListener());
        }
    }

    /**
     * Enable/disable notification board
     *
     * @param enable
     */
    public void setBoardEnabled(boolean enable) {
        final NotificationRootView root = mWindow.mRoot;
        root.setBoardEnabled(enable);
        if (enable && mBoard == null) {
            mBoard = root.getBoard();
            mBoard.addStateListener(new BoardStateListener());
        }
    }

    /**
     * Set callback for notification view.
     *
     * @param cb
     */
    public void setViewCallback(NotificationViewCallback cb) {
        if (mView != null) {
            mView.setCallback(cb);
        }
    }

    /**
     * Set callback for notification board.
     *
     * @param cb
     */
    public void setBoardCallback(NotificationBoardCallback cb) {
        if (mBoard != null) {
            mBoard.setCallback(cb);
        }
    }

    /**
     * Dismiss notification view.
     */
    public void dismissView() {
        if (mView != null) {
            mView.dismiss();
        }
    }

    /**
     * Open notification board.
     */
    public void openBoard() {
        if (mBoard != null) {
            mBoard.open(true);
        }
    }

    /**
     * Close notification board.
     */
    public void closeBoard() {
        if (mBoard != null) {
            mBoard.close(true);
        }
    }

    @Override
    protected void onCancel(NotificationEntry entry) {
        if (mView != null) {
            mView.onCancel(entry);
        } else {
            onCancelFinished(entry);
        }
    }

    @Override
    protected void onCancelAll() {
        if (mView != null) {
            mView.onCancelAll();
        } else {
            onCancelAllFinished();
        }
    }

    @Override
    protected void onArrival(NotificationEntry entry) {
        if (mView == null) {
            Log.w(TAG, "NotificationView not found.");
            onSendIgnored(entry);
            return;
        }

        if (!mView.hasCallback()) {
            if (DBG) Log.v(TAG, "set default NotificationViewCallback.");
            mView.setCallback(new ViewCallback());
        }

        if (!mView.isViewEnabled()) {
            if (DBG) Log.v(TAG, "NotificationView is currently disabled.");
            onSendIgnored(entry);
            return;
        }

        mView.onArrival(entry);
    }

    @Override
    protected void onUpdate(NotificationEntry entry) {
        if (mView == null) {
            Log.w(TAG, "NotificationView not found.");
            onUpdateIgnored(entry);
            return;
        }

        if (!mView.isViewEnabled()) {
            if (DBG) Log.v(TAG, "NotificationView is currently disabled.");
            onUpdateIgnored(entry);
            return;
        }

        mView.onUpdate(entry);
    }

    private final class ViewStateListener extends NotificationView.SimpleStateListener {

        @Override
        public void onViewTicking(NotificationView view) {
            if (DBG) Log.v(TAG, "onViewTicking");
            if (mBoard == null || !mBoard.isShowing()) {
                mWindow.attach();
            }
        }

        @Override
        public void onViewDismiss(NotificationView view) {
            if (DBG) Log.v(TAG, "onViewDismiss");
            if (mBoard == null || !mBoard.isShowing()) {
                mWindow.detach();
            }
        }
    }

    private final class BoardStateListener extends NotificationBoard.SimpleStateListener {

        @Override
        public void onBoardPrepare(NotificationBoard board) {
            if (DBG) Log.v(TAG, "onBoardPrepare");
            if (mView == null || !mView.isTicking()) {
                mWindow.attach();
            }
            mWindow.expand(true);
        }

        @Override
        public void onBoardEndOpen(NotificationBoard board) {
            if (DBG) Log.v(TAG, "onBoardEndOpen");
        }

        @Override
        public void onBoardEndClose(NotificationBoard board) {
            if (DBG) Log.v(TAG, "onBoardEndClose");
            if (mView == null || !mView.isTicking()) {
                mWindow.detach();
            }
            mWindow.expand(false);
        }
    }

    private final class NotificationWindow extends FrameLayout {

        private WindowManager mWindowManager;
        private WindowManager.LayoutParams mLayoutParams;
        private NotificationRootView mRoot;
        private NotificationBoard mBoard;
        private NotificationView mView;
        private boolean mAttached;

        NotificationWindow(Context context) {
            super(context);

            mWindowManager = (WindowManager)
                mContext.getSystemService(Context.WINDOW_SERVICE);

            mRoot = new NotificationRootView(mContext);
            addView(mRoot);

            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            context.registerReceiver(mReceiver, filter);
        }

        void expand(boolean expand) {
            WindowManager.LayoutParams lp = getWindowLayoutParams();
            lp.height = expand ? WindowManager.LayoutParams.MATCH_PARENT :
                WindowManager.LayoutParams.WRAP_CONTENT;
            mWindowManager.updateViewLayout(this, lp);
        }

        private void attach() {
            if (!mAttached) mWindowManager.addView(this, getWindowLayoutParams());
        }

        private void detach() {
            if (mAttached) mWindowManager.removeView(this);
        }

        private void onBackKey() {
            mRoot.onBackKey();
        }

        private void onHomeKey() {
            mRoot.onHomeKey();
        }

        private WindowManager.LayoutParams getWindowLayoutParams() {
            if (mLayoutParams == null) {
                mLayoutParams = new WindowManager.LayoutParams();
                mLayoutParams.gravity = Gravity.TOP;
                mLayoutParams.setTitle(getClass().getSimpleName());
                mLayoutParams.packageName = mContext.getPackageName();
                mLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
                mLayoutParams.format = PixelFormat.TRANSLUCENT;
                mLayoutParams.flags |=
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
                mLayoutParams.x = 0;
                mLayoutParams.y = 0;
                mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
                mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            }
            return mLayoutParams;
        }

        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

                @Override
                public void onReceive(Context context, Intent intent) {
                    if ("homekey".equals(intent.getStringExtra("reason"))) {
                        onHomeKey();
                    }
                }
            };

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
                event.getAction() == KeyEvent.ACTION_UP) {
                onBackKey();
                return true;
            }
            return super.dispatchKeyEvent(event);
        }

        @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            if (DBG) Log.v(TAG, "attached.");
            mAttached = true;
        }

        @Override
        public void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            if (DBG) Log.v(TAG, "detached.");
            mAttached = false;
        }
    }

    @Override public String toSimpleString() { return SIMPLE_NAME; }
    @Override public String toString() { return SIMPLE_NAME; }
}