/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.documentsui;

import static com.android.documentsui.base.SharedMinimal.DEBUG;

import android.app.Activity;
import android.util.Log;
import android.view.Gravity;
import android.view.View;

import androidx.annotation.ColorRes;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener;
import androidx.legacy.app.ActionBarDrawerToggle;

import com.android.documentsui.base.Display;
import com.android.documentsui.base.Providers;

/**
 * A facade over the various pieces comprising "roots fragment in a Drawer".
 *
 * @see DrawerController#create(DrawerLayout)
 */
public abstract class DrawerController implements DrawerListener {
    public static final String TAG = "DrawerController";

    public abstract void update();
    public abstract void setOpen(boolean open);
    public abstract void setLocked(boolean locked);
    public abstract boolean isPresent();
    public abstract boolean isOpen();
    abstract void setTitle(String title);

    /**
     * Returns a controller suitable for {@code Layout}.
     */
    public static DrawerController create(BaseActivity activity, ActivityConfig activityConfig) {

        DrawerLayout layout = (DrawerLayout) activity.findViewById(R.id.drawer_layout);

        if (layout == null) {
            return new StubDrawerController();
        }

        View drawer = activity.findViewById(R.id.drawer_roots);
        Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
        drawer.getLayoutParams().width = calculateDrawerWidth(activity);

        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                activity,
                layout,
                R.drawable.ic_hamburger,
                R.string.drawer_open,
                R.string.drawer_close);

        return new RuntimeDrawerController(layout, drawer, toggle, toolbar, activityConfig,
                activity);
    }

    /**
     * Returns a controller suitable for {@code Layout}.
     */
    static DrawerController createStub() {
        return new StubDrawerController();
    }

    private static int calculateDrawerWidth(Activity activity) {
        // Material design specification for navigation drawer:
        // https://www.google.com/design/spec/patterns/navigation-drawer.html
        float width = Display.screenWidth(activity) - Display.actionBarHeight(activity);
        float maxWidth = activity.getResources().getDimension(R.dimen.max_drawer_width);
        int finalWidth = (int) ((width > maxWidth ? maxWidth : width));

        if (DEBUG)
            Log.d(TAG, "Calculated drawer width:" + (finalWidth / Display.density(activity)));

        return finalWidth;
    }

    /**
     * Runtime controller that manages a real drawer.
     */
    private static final class RuntimeDrawerController extends DrawerController
            implements ItemDragListener.DragHost {
        private static final int SPRING_TIMEOUT = 750;
        private final ActionBarDrawerToggle mToggle;
        private DrawerLayout mLayout;
        private View mDrawer;
        private Toolbar mToolbar;
        private AbstractActionHandler.CommonAddons mCommonAddons;

        public RuntimeDrawerController(
                DrawerLayout layout,
                View drawer,
                ActionBarDrawerToggle toggle,
                Toolbar drawerToolbar,
                ActivityConfig activityConfig,
                AbstractActionHandler.CommonAddons commonAddons) {
            mToolbar = drawerToolbar;
            assert(layout != null);

            mLayout = layout;
            mDrawer = drawer;
            mToggle = toggle;
            mCommonAddons = commonAddons;

            mLayout.setDrawerListener(this);

            if (activityConfig.dragAndDropEnabled()) {
                View edge = layout.findViewById(R.id.drawer_edge);
                edge.setOnDragListener(new ItemDragListener<>(this, SPRING_TIMEOUT));
            }
        }

        @Override
        public void runOnUiThread(Runnable runnable) {
            mDrawer.post(runnable);
        }

        @Override
        public void setDropTargetHighlight(View v, boolean highlight) {
            assert (v.getId() == R.id.drawer_edge);

            @ColorRes int id = highlight ? R.color.secondary :
                android.R.color.transparent;
            v.setBackgroundColor(id);
        }

        @Override
        public void onDragEntered(View v) {
            // do nothing; let drawer only open for onViewHovered
        }

        @Override
        public void onDragExited(View v) {
            // do nothing
        }

        @Override
        public void onViewHovered(View v) {
            assert (v.getId() == R.id.drawer_edge);

            setOpen(true);
        }

        @Override
        public void onDragEnded() {
            // do nothing
        }

        @Override
        public void setOpen(boolean open) {
            View list = mDrawer.findViewById(R.id.roots_list);
            if (open) {
                mLayout.openDrawer(mDrawer);
                if (list != null) {
                    mDrawer.requestFocus();
                }
            } else {
                mLayout.closeDrawer(mDrawer);
                if (list != null) {
                    mDrawer.clearFocus();
                }
            }
        }

        @Override
        public void setLocked(boolean locked) {
            if (locked) {
                mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
            } else {
                mLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
            }
        }

        @Override
        public boolean isOpen() {
            return mLayout.isDrawerOpen(mDrawer);
        }

        @Override
        public boolean isPresent() {
            return DrawerLayout.LOCK_MODE_UNLOCKED
                    == mLayout.getDrawerLockMode(Gravity.START);
        }

        @Override
        void setTitle(String title) {
            mToolbar.setTitle(title);
        }

        @Override
        public void update() {
            mToggle.syncState();
        }

        @Override
        public void onDrawerSlide(View drawerView, float slideOffset) {
            mToggle.onDrawerSlide(drawerView, slideOffset);
        }

        @Override
        public void onDrawerOpened(View drawerView) {
            mToggle.onDrawerOpened(drawerView);
            // Update the information for Storage's root
            DocumentsApplication.getProvidersCache(drawerView.getContext()).updateAuthorityAsync(
                    mCommonAddons.getSelectedUser(), Providers.AUTHORITY_STORAGE);
        }

        @Override
        public void onDrawerClosed(View drawerView) {
            mToggle.onDrawerClosed(drawerView);
        }

        @Override
        public void onDrawerStateChanged(int newState) {
            mToggle.onDrawerStateChanged(newState);
        }
    }

    /*
     * Stub controller useful with clients that don't host a real drawer.
     */
    private static final class StubDrawerController extends DrawerController {

        @Override
        public void setOpen(boolean open) {}

        @Override
        public void setLocked(boolean locked) {}

        @Override
        public boolean isOpen() {
            return false;
        }

        @Override
        public boolean isPresent() {
            return false;
        }

        @Override
        void setTitle(String title) {}

        @Override
        public void update() {}

        @Override
        public void onDrawerSlide(View drawerView, float slideOffset) {}

        @Override
        public void onDrawerOpened(View drawerView) {}

        @Override
        public void onDrawerClosed(View drawerView) {}

        @Override
        public void onDrawerStateChanged(int newState) {}
    }
}
