/*
 * 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.layoutlib.bridge.impl;

import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
import com.android.layoutlib.bridge.bars.BridgeActionBar;
import com.android.layoutlib.bridge.bars.Config;
import com.android.layoutlib.bridge.bars.FrameworkActionBar;
import com.android.layoutlib.bridge.bars.NavigationBar;
import com.android.layoutlib.bridge.bars.StatusBar;
import com.android.layoutlib.bridge.bars.TitleBar;
import com.android.resources.Density;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;

import android.annotation.NonNull;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.VERTICAL;
import static com.android.layoutlib.bridge.impl.ResourceHelper.getBooleanThemeValue;

/**
 * The Layout used to create the system decor.
 *
 * The layout inflated will contain a content frame where the user's layout can be inflated.
 * <pre>
 *  +-------------------------------------------------+---+
 *  | Status bar                                      | N |
 *  +-------------------------------------------------+ a |
 *  | Title/Action bar (optional)                     | v |
 *  +-------------------------------------------------+   |
 *  | Content, vertical extending                     | b |
 *  |                                                 | a |
 *  |                                                 | r |
 *  +-------------------------------------------------+---+
 * </pre>
 * or
 * <pre>
 *  +-------------------------------------+
 *  | Status bar                          |
 *  +-------------------------------------+
 *  | Title/Action bar (optional)         |
 *  +-------------------------------------+
 *  | Content, vertical extending         |
 *  |                                     |
 *  |                                     |
 *  +-------------------------------------+
 *  | Nav bar                             |
 *  +-------------------------------------+
 * </pre>
 *
 */
class Layout extends RelativeLayout {

    // Theme attributes used for configuring appearance of the system decor.
    private static final String ATTR_WINDOW_FLOATING = "windowIsFloating";
    private static final String ATTR_WINDOW_BACKGROUND = "windowBackground";
    private static final String ATTR_WINDOW_FULL_SCREEN = "windowFullscreen";
    private static final String ATTR_NAV_BAR_HEIGHT = "navigation_bar_height";
    private static final String ATTR_NAV_BAR_WIDTH = "navigation_bar_width";
    private static final String ATTR_STATUS_BAR_HEIGHT = "status_bar_height";
    private static final String ATTR_WINDOW_ACTION_BAR = "windowActionBar";
    private static final String ATTR_ACTION_BAR_SIZE = "actionBarSize";
    private static final String ATTR_WINDOW_NO_TITLE = "windowNoTitle";
    private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
    private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
    private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
    private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";

    // Default sizes
    private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
    private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
    private static final int DEFAULT_NAV_BAR_SIZE = 48;

    // Ids assigned to components created. This is so that we can refer to other components in
    // layout params.
    private static final String ID_NAV_BAR = "navBar";
    private static final String ID_STATUS_BAR = "statusBar";
    private static final String ID_TITLE_BAR = "titleBar";
    // Prefix used with the above ids in order to make them unique in framework namespace.
    private static final String ID_PREFIX = "android_layoutlib_";

    /**
     * Temporarily store the builder so that it doesn't have to be passed to all methods used
     * during inflation.
     */
    private Builder mBuilder;

    /**
     * This holds user's layout.
     */
    private FrameLayout mContentRoot;

    public Layout(@NonNull Builder builder) {
        super(builder.mContext);
        mBuilder = builder;
        if (builder.mWindowBackground != null) {
            Drawable d = ResourceHelper.getDrawable(builder.mWindowBackground, builder.mContext);
            setBackground(d);
        }

        int simulatedPlatformVersion = getParams().getSimulatedPlatformVersion();
        HardwareConfig hwConfig = getParams().getHardwareConfig();
        Density density = hwConfig.getDensity();
        boolean isRtl = Bridge.isLocaleRtl(getParams().getLocale());
        setLayoutDirection(isRtl? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR);

        NavigationBar navBar = null;
        if (mBuilder.hasNavBar()) {
            navBar = createNavBar(getContext(), density, isRtl, getParams().isRtlSupported(),
                    simulatedPlatformVersion);
        }

        StatusBar statusBar = null;
        if (builder.mStatusBarSize > 0) {
            statusBar = createStatusBar(getContext(), density, isRtl, getParams().isRtlSupported(),
                    simulatedPlatformVersion);
        }

        View actionBar = null;
        TitleBar titleBar = null;
        if (builder.mActionBarSize > 0) {
            BridgeActionBar bar = createActionBar(getContext(), getParams());
            mContentRoot = bar.getContentRoot();
            actionBar = bar.getRootView();
        } else if (mBuilder.mTitleBarSize > 0) {
            titleBar = createTitleBar(getContext(), getParams().getAppLabel(),
                    simulatedPlatformVersion);
        }

        addViews(titleBar, mContentRoot == null ? (mContentRoot = createContentFrame()) : actionBar,
                statusBar, navBar);
        // Done with the builder. Don't hold a reference to it.
        mBuilder = null;
     }

    @NonNull
    private FrameLayout createContentFrame() {
        FrameLayout contentRoot = new FrameLayout(getContext());
        LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
        int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
        if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
            params.addRule(rule, getId(ID_NAV_BAR));
        }
        int below = -1;
        if (mBuilder.mActionBarSize <= 0 && mBuilder.mTitleBarSize > 0) {
            below = getId(ID_TITLE_BAR);
        } else if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
            below = getId(ID_STATUS_BAR);
        }
        if (below != -1) {
            params.addRule(BELOW, below);
        }
        contentRoot.setLayoutParams(params);
        return contentRoot;
    }

    @NonNull
    private LayoutParams createLayoutParams(int width, int height) {
        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        if (width > 0) {
            width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics);
        }
        if (height > 0) {
            height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics);
        }
        return new LayoutParams(width, height);
    }

    @NonNull
    public FrameLayout getContentRoot() {
        return mContentRoot;
    }

    @NonNull
    private SessionParams getParams() {
        return mBuilder.mParams;
    }

    @NonNull
    @Override
    public BridgeContext getContext(){
        return (BridgeContext) super.getContext();
    }

    /**
     * @param isRtl    whether the current locale is an RTL locale.
     * @param isRtlSupported    whether the applications supports RTL (i.e. has supportsRtl=true
     * in the manifest and targetSdkVersion >= 17.
     */
    @NonNull
    private StatusBar createStatusBar(BridgeContext context, Density density, boolean isRtl,
            boolean isRtlSupported, int simulatedPlatformVersion) {
        StatusBar statusBar =
                new StatusBar(context, density, isRtl, isRtlSupported, simulatedPlatformVersion);
        LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mStatusBarSize);
        if (mBuilder.isNavBarVertical()) {
            params.addRule(START_OF, getId(ID_NAV_BAR));
        }
        statusBar.setLayoutParams(params);
        statusBar.setId(getId(ID_STATUS_BAR));
        return statusBar;
    }

    private BridgeActionBar createActionBar(@NonNull BridgeContext context,
            @NonNull SessionParams params) {
        BridgeActionBar actionBar;
        if (mBuilder.isThemeAppCompat()) {
            actionBar = new AppCompatActionBar(context, params);
        } else {
            actionBar = new FrameworkActionBar(context, params);
        }
        LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
        int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
        if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
            layoutParams.addRule(rule, getId(ID_NAV_BAR));
        }
        if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
            layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
        }
        actionBar.getRootView().setLayoutParams(layoutParams);
        actionBar.createMenuPopup();
        return actionBar;
    }

    @NonNull
    private TitleBar createTitleBar(BridgeContext context, String title,
            int simulatedPlatformVersion) {
        TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
        LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
        if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
            params.addRule(BELOW, getId(ID_STATUS_BAR));
        }
        if (mBuilder.isNavBarVertical() && mBuilder.solidBars()) {
            params.addRule(START_OF, getId(ID_NAV_BAR));
        }
        titleBar.setLayoutParams(params);
        titleBar.setId(getId(ID_TITLE_BAR));
        return titleBar;
    }

    /**
     * @param isRtl    whether the current locale is an RTL locale.
     * @param isRtlSupported    whether the applications supports RTL (i.e. has supportsRtl=true
     * in the manifest and targetSdkVersion >= 17.
     */
    @NonNull
    private NavigationBar createNavBar(BridgeContext context, Density density, boolean isRtl,
            boolean isRtlSupported, int simulatedPlatformVersion) {
        int orientation = mBuilder.mNavBarOrientation;
        int size = mBuilder.mNavBarSize;
        NavigationBar navBar = new NavigationBar(context, density, orientation, isRtl,
                isRtlSupported, simulatedPlatformVersion);
        boolean isVertical = mBuilder.isNavBarVertical();
        int w = isVertical ? size : MATCH_PARENT;
        int h = isVertical ? MATCH_PARENT : size;
        LayoutParams params = createLayoutParams(w, h);
        params.addRule(isVertical ? ALIGN_PARENT_END : ALIGN_PARENT_BOTTOM);
        navBar.setLayoutParams(params);
        navBar.setId(getId(ID_NAV_BAR));
        return navBar;
    }

    private void addViews(@NonNull View... views) {
        for (View view : views) {
            if (view != null) {
                addView(view);
            }
        }
    }

    private int getId(String name) {
        return Bridge.getResourceId(ResourceType.ID, ID_PREFIX + name);
    }

    /**
     * A helper class to help initialize the Layout.
     */
    static class Builder {
        @NonNull
        private final SessionParams mParams;
        @NonNull
        private final BridgeContext mContext;
        private final RenderResources mResources;
        
        private final boolean mWindowIsFloating;
        private ResourceValue mWindowBackground;
        private int mStatusBarSize;
        private int mNavBarSize;
        private int mNavBarOrientation;
        private int mActionBarSize;
        private int mTitleBarSize;
        private boolean mTranslucentStatus;
        private boolean mTranslucentNav;

        private Boolean mIsThemeAppCompat;

        public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
            mParams = params;
            mContext = context;
            mResources = mParams.getResources();
            mWindowIsFloating = getBooleanThemeValue(mResources, ATTR_WINDOW_FLOATING, true, true);
            
            findBackground();

            if (!mParams.isForceNoDecor()) {
                findStatusBar();
                findActionBar();
                findNavBar();
            }
        }

        private void findBackground() {
            if (!mParams.isBgColorOverridden()) {
                mWindowBackground = mResources.findItemInTheme(ATTR_WINDOW_BACKGROUND, true);
                mWindowBackground = mResources.resolveResValue(mWindowBackground);
            }
        }

        private void findStatusBar() {
            boolean windowFullScreen =
                    getBooleanThemeValue(mResources, ATTR_WINDOW_FULL_SCREEN, true, false);
            if (!windowFullScreen && !mWindowIsFloating) {
                mStatusBarSize =
                        getDimension(ATTR_STATUS_BAR_HEIGHT, true, DEFAULT_STATUS_BAR_HEIGHT);
                mTranslucentStatus = getBooleanThemeValue(mResources,
                        ATTR_WINDOW_TRANSLUCENT_STATUS, true, false);
            }
        }

        private void  findActionBar() {
            if (mWindowIsFloating) {
                return;
            }
            // Check if an actionbar is needed
            boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
                    !isThemeAppCompat(), true);
            if (windowActionBar) {
                mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
            } else {
                // Maybe the gingerbread era title bar is needed
                boolean windowNoTitle =
                        getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, true, false);
                if (!windowNoTitle) {
                    mTitleBarSize =
                            getDimension(ATTR_WINDOW_TITLE_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
                }
            }
        }

        private void findNavBar() {
            if (hasSoftwareButtons() && !mWindowIsFloating) {

                // get orientation
                HardwareConfig hwConfig = mParams.getHardwareConfig();
                boolean barOnBottom = true;

                if (hwConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
                    int shortSize = hwConfig.getScreenHeight();
                    int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
                            hwConfig.getDensity().getDpiValue();

                    // 0-599dp: "phone" UI with bar on the side
                    // 600+dp: "tablet" UI with bar on the bottom
                    barOnBottom = shortSizeDp >= 600;
                }

                mNavBarOrientation = barOnBottom ? LinearLayout.HORIZONTAL : VERTICAL;
                mNavBarSize = getDimension(barOnBottom ? ATTR_NAV_BAR_HEIGHT : ATTR_NAV_BAR_WIDTH,
                        true, DEFAULT_NAV_BAR_SIZE);
                mTranslucentNav = getBooleanThemeValue(mResources,
                        ATTR_WINDOW_TRANSLUCENT_NAV, true, false);
            }
        }

        private int getDimension(String attr, boolean isFramework, int defaultValue) {
            ResourceValue value = mResources.findItemInTheme(attr, isFramework);
            value = mResources.resolveResValue(value);
            if (value != null) {
                TypedValue typedValue = ResourceHelper.getValue(attr, value.getValue(), true);
                if (typedValue != null) {
                    return (int) typedValue.getDimension(mContext.getMetrics());
                }
            }
            return defaultValue;
        }

        private boolean hasSoftwareButtons() {
            return mParams.getHardwareConfig().hasSoftwareButtons();
        }

        private boolean isThemeAppCompat() {
            // If a cached value exists, return it.
            if (mIsThemeAppCompat != null) {
                return mIsThemeAppCompat;
            }
            // Ideally, we should check if the corresponding activity extends
            // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
            StyleResourceValue defaultTheme = mResources.getDefaultTheme();
            // We can't simply check for parent using resources.themeIsParentOf() since the
            // inheritance structure isn't really what one would expect. The first common parent
            // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
            boolean isThemeAppCompat = false;
            for (int i = 0; i < 50; i++) {
                if (defaultTheme == null) {
                    break;
                }
                // for loop ensures that we don't run into cyclic theme inheritance.
                if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
                    isThemeAppCompat = true;
                    break;
                }
                defaultTheme = mResources.getParent(defaultTheme);
            }
            mIsThemeAppCompat = isThemeAppCompat;
            return isThemeAppCompat;
        }

        /**
         * Return true if the status bar or nav bar are present, they are not translucent (i.e
         * content doesn't overlap with them).
         */
        private boolean solidBars() {
            return !(hasNavBar() && mTranslucentNav) && !(hasStatusBar() && mTranslucentStatus);
        }

        private boolean hasNavBar() {
            return Config.showOnScreenNavBar(mParams.getSimulatedPlatformVersion()) &&
                    hasSoftwareButtons() && mNavBarSize > 0;
        }

        private boolean hasStatusBar() {
            return mStatusBarSize > 0;
        }

        /**
         * Return true if the nav bar is present and is vertical.
         */
        private boolean isNavBarVertical() {
            return hasNavBar() && mNavBarOrientation == VERTICAL;
        }
    }
}
