/*
 * Copyright (C) 2015 Mantas Palaima
 *
 * 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 io.palaima.debugdrawer;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntegerRes;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.v4.widget.DrawerLayout;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ScrollView;

import java.util.ArrayList;
import java.util.List;

import io.palaima.debugdrawer.base.DebugModule;
import io.palaima.debugdrawer.util.UIUtils;
import io.palaima.debugdrawer.view.DebugView;
import io.palaima.debugdrawer.view.ScrimInsetsFrameLayout;

public class DebugDrawer {

    private final DrawerLayout drawerLayout;

    private ScrollView sliderLayout;
    private DebugView debugView;

    private int drawerGravity;

    private DebugDrawer(Builder builder) {
        drawerLayout = builder.drawerLayout;
        drawerGravity = builder.drawerGravity;
        sliderLayout = builder.sliderLayout;
        debugView = builder.debugView;
    }

    /**
     * Open the drawer
     */
    public void openDrawer() {
        if (drawerLayout != null && sliderLayout != null) {
            if (drawerGravity != 0) {
                drawerLayout.openDrawer(drawerGravity);
            } else {
                drawerLayout.openDrawer(sliderLayout);
            }
        }
    }

    /**
     * close the drawer
     */
    public void closeDrawer() {
        if (drawerLayout != null) {
            if (drawerGravity != 0) {
                drawerLayout.closeDrawer(drawerGravity);
            } else {
                drawerLayout.closeDrawer(sliderLayout);
            }
        }
    }

    /**
     * Get the current state of the drawer.
     * True if the drawer is currently open.
     *
     * @return
     */
    public boolean isDrawerOpen() {
        if (drawerLayout != null && sliderLayout != null) {
            return drawerLayout.isDrawerOpen(sliderLayout);
        }
        return false;
    }

    /**
     * Enable or disable interaction with all drawers.
     *
     * @return
     */
    public void setDrawerLockMode(int lockMode) {
        if (drawerLayout != null && sliderLayout != null) {
            drawerLayout.setDrawerLockMode(lockMode);
        }
    }

    /**
     * Calls modules {@link DebugModule#onResume()} method
     */
    void onResume() {
        debugView.onResume();
    }

    /**
     * Calls modules {@link DebugModule#onPause()} method
     */
    void onPause() {
        debugView.onPause();
    }

    /**
     * Starts all modules and calls their {@link DebugModule#onStart()} method
     */
    void onStart() {
        debugView.onStart();
    }

    /**
     * Removes all modules and calls their {@link DebugModule#onStop()} method
     */
    void onStop() {
        debugView.onStop();
    }

    public static class Builder {

        private ViewGroup rootView;

        private Activity activity;

        private DrawerLayout drawerLayout;

        private ScrollView sliderLayout;

        private int drawerGravity = Gravity.END;

        //the width of the drawer
        private int drawerWidth = -1;

        private DebugModule[] drawerItems;

        private DrawerLayout.DrawerListener onDrawerListener;

        private int sliderBackgroundColor = 0;

        private int sliderBackgroundColorRes = -1;

        private Drawable sliderBackgroundDrawable;

        private int sliderBackgroundDrawableRes = -1;

        private int themeRes = -1;

        private DebugView debugView;

        private ScrimInsetsFrameLayout drawerContentRoot;

        /**
         * Pass the activity you use the drawer in ;)
         * This is required if you want to set any values by resource
         */
        public Builder(Activity activity) {
            this.rootView = (ViewGroup) activity.findViewById(android.R.id.content);
            this.activity = activity;
        }

        /**
         * Pass the rootView of the Drawer which will be used to inflate the DrawerLayout in
         */
        public Builder rootView(ViewGroup rootView) {
            this.rootView = rootView;
            return this;
        }

        /**
         * Pass the rootView as resource of the Drawer which will be used to inflate the
         * DrawerLayout in
         */
        public Builder rootView(int rootViewRes) {
            if (activity == null) {
                throw new RuntimeException("please pass an activity first to use this call");
            }

            return rootView((ViewGroup) activity.findViewById(rootViewRes));
        }

        /**
         * Set the gravity for the drawer. START, LEFT | RIGHT, END
         */
        public Builder gravity(int gravity) {
            this.drawerGravity = gravity;
            return this;
        }

        /**
         * Set the Drawer width with a pixel value
         */
        public Builder widthPx(int drawerWidthPx) {
            this.drawerWidth = drawerWidthPx;
            return this;
        }

        /**
         * Set the Drawer width with a dp value
         */
        public Builder widthDp(int drawerWidthDp) {
            if (activity == null) {
                throw new RuntimeException("please pass an activity first to use this call");
            }
            this.drawerWidth = (int) TypedValue.applyDimension(1, drawerWidthDp,
                activity.getResources().getDisplayMetrics());
            return this;
        }

        /**
         * Set the Drawer width with a dimension resource
         */
        public Builder widthRes(int drawerWidthRes) {
            if (activity == null) {
                throw new RuntimeException("please pass an activity first to use this call");
            }

            this.drawerWidth = activity.getResources().getDimensionPixelSize(drawerWidthRes);
            return this;
        }

        /**
         * Set the background color for the Slider.
         * This is the view containing the list.
         */
        public Builder backgroundColor(int sliderBackgroundColor) {
            this.sliderBackgroundColor = sliderBackgroundColor;
            return this;
        }

        /**
         * Set the background color for the Slider from a Resource.
         * This is the view containing the list.
         */
        public Builder backgroundColorRes(@IntegerRes int sliderBackgroundColorRes) {
            this.sliderBackgroundColorRes = sliderBackgroundColorRes;
            return this;
        }


        /**
         * Set the background drawable for the Slider.
         * This is the view containing the list.
         */
        public Builder backgroundDrawable(Drawable sliderBackgroundDrawable) {
            this.sliderBackgroundDrawable = sliderBackgroundDrawable;
            return this;
        }


        /**
         * Set the background drawable for the Slider from a Resource.
         * This is the view containing the list.
         */
        public Builder backgroundDrawableRes(@DrawableRes int sliderBackgroundDrawableRes) {
            this.sliderBackgroundDrawableRes = sliderBackgroundDrawableRes;
            return this;
        }

        public Builder setDrawerListener(DrawerLayout.DrawerListener onDrawerListener) {
            this.onDrawerListener = onDrawerListener;
            return this;
        }

        public Builder withTheme(@StyleRes int themeRes) {
            this.themeRes = themeRes;
            return this;
        }

        /**
         * Add a initial DrawerItem or a DrawerItem Array for the Drawer
         */
        public Builder modules(DebugModule... drawerItems) {
            this.drawerItems = drawerItems;
            return this;
        }

        /**
         * Build and add the Drawer to your activity
         *
         * @return
         */
        public DebugDrawer build() {
            if (activity == null) {
                throw new RuntimeException("please pass an activity");
            }

            if (rootView == null || rootView.getChildCount() == 0) {
                throw new RuntimeException(
                    "You have to set your layout for this activity with setContentView() first.");
            }

            LayoutInflater layoutInflater = activity.getLayoutInflater();
            if (themeRes > 0) {
                layoutInflater = layoutInflater.cloneInContext(new ContextThemeWrapper(activity, themeRes));
            }
            drawerLayout = (DrawerLayout) layoutInflater
                .inflate(R.layout.dd_debug_drawer, rootView, false);

            final boolean alreadyInflated = rootView.getChildAt(0).getId() == R.id.dd_drawer_layout;

            //store the original content views
            final List<View> originalContentViews = new ArrayList<>(rootView.getChildCount());
            for (int i = 0; i < rootView.getChildCount(); i++) {
                originalContentViews.add(rootView.getChildAt(i));
            }

            //get the drawer root
            drawerContentRoot = (ScrimInsetsFrameLayout) drawerLayout.getChildAt(0);

            //only add the new layout if it wasn't done before
            if (!alreadyInflated) {
                // remove the contentView
                for (View contentView : originalContentViews) {
                    rootView.removeView(contentView);
                }
            } else {
                //if it was already inflated we have to clean up again
                rootView.removeAllViews();
            }

            //create the layoutParams to use for the contentView
            final FrameLayout.LayoutParams layoutParamsContentView = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            );

            //add the original content Views to the drawer content frameLayout
            for (View contentView : originalContentViews) {
                drawerContentRoot.addView(contentView, layoutParamsContentView);
            }

            //add the drawerLayout to the root
            rootView.addView(drawerLayout, new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            ));

            final DrawerLayout.DrawerListener listener = new DrawerLayout.DrawerListener() {
                @Override
                public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
                    if (onDrawerListener != null) {
                        onDrawerListener.onDrawerSlide(drawerView, slideOffset);
                    }
                }

                @Override
                public void onDrawerOpened(@NonNull View drawerView) {
                    if (onDrawerListener != null) {
                        onDrawerListener.onDrawerOpened(drawerView);
                    }
                    if (drawerItems != null && drawerItems.length != 0) {
                        for (DebugModule drawerItem : drawerItems) {
                            drawerItem.onOpened();
                        }
                    }
                }

                @Override
                public void onDrawerClosed(@NonNull View drawerView) {
                    if (onDrawerListener != null) {
                        onDrawerListener.onDrawerClosed(drawerView);
                    }
                    if (drawerItems != null && drawerItems.length != 0) {
                        for (DebugModule drawerItem : drawerItems) {
                            drawerItem.onClosed();
                        }
                    }
                }

                @Override
                public void onDrawerStateChanged(int newState) {

                }
            };

            rootView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {

                @Override
                public void onViewAttachedToWindow(View view) {
                    if (drawerLayout != null) {
                        drawerLayout.addDrawerListener(listener);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View view) {
                    if (drawerLayout != null) {
                        drawerLayout.removeDrawerListener(listener);
                    }
                    if (rootView != null) {
                        rootView.removeOnAttachStateChangeListener(this);
                    }
                }
            });

            sliderLayout = drawerLayout.findViewById(R.id.dd_slider_layout);
            debugView = sliderLayout.findViewById(R.id.dd_debug_view);

            // get the layout params
            DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) sliderLayout.getLayoutParams();
            if (params != null) {
                // if we've set a custom gravity set it
                if (drawerGravity != 0) {
                    params.gravity = drawerGravity;
                }
                // if this is a drawer from the right, change the margins :D
                params = processDrawerLayoutParams(params);
                // set the new layout params
                sliderLayout.setLayoutParams(params);
            }

            // set the background
            if (sliderBackgroundColor != 0) {
                sliderLayout.setBackgroundColor(sliderBackgroundColor);
            } else if (sliderBackgroundColorRes != -1) {
                sliderLayout.setBackgroundColor(activity.getResources().getColor(sliderBackgroundColorRes));
            } else if (sliderBackgroundDrawable != null) {
                UIUtils.setBackground(sliderLayout, sliderBackgroundDrawable);
            } else if (sliderBackgroundDrawableRes != -1) {
                UIUtils.setBackground(sliderLayout, sliderBackgroundColorRes);
            }

            debugView.modules(drawerItems);

            //create the result object
            final DebugDrawer result = new DebugDrawer(this);

            //register a lifecycle callback for start, stop, pause and resume events
            activity.getApplication().registerActivityLifecycleCallbacks(new DebugDrawerLifecycleCallbacks(activity, result));

            //forget the reference to the activity
            activity = null;


            return result;
        }

        /**
         * helper to extend the layoutParams of the drawer
         *
         * @param params
         * @return
         */
        private DrawerLayout.LayoutParams processDrawerLayoutParams(DrawerLayout.LayoutParams params) {
            if (params != null) {
                if (drawerGravity != 0 && (drawerGravity == Gravity.RIGHT || drawerGravity == Gravity.END)) {
                    params.rightMargin = 0;
                    if (Build.VERSION.SDK_INT >= 17) {
                        params.setMarginEnd(0);
                    }

                    params.leftMargin = activity.getResources().getDimensionPixelSize(R.dimen.dd_debug_drawer_margin);
                    if (Build.VERSION.SDK_INT >= 17) {
                        params.setMarginEnd(activity.getResources().getDimensionPixelSize(R.dimen.dd_debug_drawer_margin));
                    }
                }

                if (drawerWidth > -1) {
                    params.width = drawerWidth;
                } else {
                    params.width = UIUtils.getOptimalDrawerWidth(activity);
                }
            }

            return params;
        }
    }
}