package com.mobify.astro.plugins.layouts;

import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import com.mobify.astro.utilities.ViewUtilities;

import org.json.JSONObject;

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

public class AnimatableSectionedContainerView extends LinearLayout {

    public static int DEFAULT_ANIMATION_DURATION = 400;

    // MiddleViewIndex is the position of the middle view if it exists, otherwise position
    // where it will be inserted
    protected int middleViewIndex;
    protected boolean hasMiddleView;
    protected List<View> hiddenViews;

    protected boolean topViewsVisible;
    protected boolean bottomViewsVisible;

    public AnimatableSectionedContainerView(Context context) {
        super(context);

        setOrientation(LinearLayout.VERTICAL);
        setLayoutTransition(getDefaultLayoutTransition(false));
        middleViewIndex = 0;
        hasMiddleView = false;
        hiddenViews = new ArrayList<View>();

        topViewsVisible = true;
        bottomViewsVisible = true;

        addMiddleView(new View(context));
    }

    // The default transition makes views appear right away,
    // and other views slide over top of it when it disappears.
    public static LayoutTransition getDefaultLayoutTransition(boolean fadesItems) {
        LayoutTransition transition = new LayoutTransition();
        transition.enableTransitionType(LayoutTransition.CHANGING);

        // Use the default behaviour of the LayoutTransition if lower than
        // Lollipop
        // In order for this custom behaviour to work properly we need to be
        // able to set the elevation on the views which is only possible in
        // lollipop and above
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);

            if (fadesItems) {
                // Default behaviour only starts the fading once spot for the new
                // item is displayed
                // We will use the bottom views
                transition.setStartDelay(LayoutTransition.APPEARING, 0);
            } else {
                transition.setAnimator(LayoutTransition.APPEARING, null);
                transition.setAnimator(LayoutTransition.DISAPPEARING,
                        ObjectAnimator.ofFloat(null, "translationY", 0f));
            }
        }

        return transition;
    }

    @Override
    public void removeViews(int start, int count) {
        for (int i = start; i < count; i++) {
            View currentView = getChildAt(i);
            if (hiddenViews.contains(currentView)) {
                hiddenViews.remove(currentView);
            }
        }
        super.removeViews(start, count);
    }

    @Override
    public void removeView(View view) {
        super.removeView(view);

        if (hiddenViews.contains(view)) {
            hiddenViews.remove(view);
        }
    }

    private static boolean viewIsVisible(int visibility) {
        return visibility == View.VISIBLE;
    }

    private static ViewGroup.LayoutParams getBasicLayoutParams() {
        return new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
    }

    private static ViewGroup.LayoutParams getLayoutParamsForMiddle() {
        // Layout parameters that stretch and fill the remaining vertical height.
        return new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                0, // height
                1f // weight
        );
    }

    // Returns LayoutParams with height in pixels
    private ViewGroup.LayoutParams getLayoutParamsForHeight(int height) {
        float scale = getContext().getResources().getDisplayMetrics().density;

        return new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                (int) (height * scale) // convert dips to pixels
        );
    }

    private boolean containedInTopContainer(View view) {
        int indexOfChild = indexOfChild(view);

        if (indexOfChild < 0) {
            return false;
        }

        if (hasTopViews() && indexOfChild <= indexAtBottomOfTopViews()) {
            return true;
        }

        return false;
    }

    private boolean containedInBottomContainer(View view) {
        int indexOfChild = indexOfChild(view);

        if (indexOfChild < 0) {
            return false;
        }

        if (hasBottomViews() && indexOfChild >= startIndexBottomView()) {
            return true;
        }

        return false;
    }

    private void addViewWithOptions(View view, JSONObject options, int index, boolean viewGroupVisible) throws Exception {
        int visibility = View.VISIBLE;

        if (options != null && options.has("visible")) {
            boolean visible = options.getBoolean("visible");
            visibility = visible ? View.VISIBLE : View.GONE;
        }

        // Add to hidden views if GONE
        if (visibility == View.GONE) {
            hiddenViews.add(view);
        }

        // Hide if viewGroup is hidden
        if (!viewGroupVisible) {
            visibility = View.GONE;
        }

        setViewElevation(view, visibility);
        view.setVisibility(visibility);

        ViewGroup.LayoutParams params;
        if (options != null && options.has("height")) {
            int height = options.getInt("height");
            params = getLayoutParamsForHeight(height);
        } else {
            params = getBasicLayoutParams();
        }
        addView(view, index, params);

    }

    private void setChildrenVisibilities(int start, int count, int visibility) {
        for (int i = start; i < start + count; i++) {
            View currentChild = getChildAt(i);
            int currentVisibility = visibility;

            if (visibility == View.VISIBLE) {
                if (hiddenViews.contains(currentChild)) {
                    currentVisibility = View.GONE;
                }
            }

            setViewElevation(currentChild, currentVisibility);
            currentChild.setVisibility(currentVisibility);
        }
    }

    protected int startIndexTopView() {
        return 0;
    }

    protected int startIndexBottomView() {
        int indexOfTopBottomItem;
        if (hasMiddleView) {
            indexOfTopBottomItem = middleViewIndex + 1;
        } else {
            indexOfTopBottomItem = middleViewIndex;
        }
        return indexOfTopBottomItem;
    }

    protected int numberTopViews() {
        return middleViewIndex;
    }

    protected int numberBottomViews() {
        int numberOfBottomItems = getChildCount() - middleViewIndex;
        if (hasMiddleView) {
            numberOfBottomItems = numberOfBottomItems - 1;
        }
        return numberOfBottomItems;
    }

    protected int indexAtBottomOfTopViews() {
        return startIndexTopView() + numberTopViews() - 1;
    }

    protected boolean hasTopViews() {
        return numberTopViews() > 0;
    }

    protected boolean hasBottomViews() {
        return numberBottomViews() > 0;
    }

    private void setChildrenElevationsGone(int start, int count) {
        for (int i = start; i < start + count; i++) {
            setViewElevation(getChildAt(i), View.GONE);
        }
    }

    private static void setViewElevation(View view, int visibility) {
        // Ensure the other views slide over the view being shown/removed
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (visibility == View.GONE || visibility == View.INVISIBLE) {
                view.setElevation(-1f);
            } else {
                view.setElevation(0f);
            }
        }
    }

    public void setAnimationDuration(int animationDuration) {
        LayoutTransition layoutTransition = getLayoutTransition();

        if (layoutTransition != null) {
            layoutTransition.setDuration(animationDuration);
        }
    }

    public void addTopView(View view, JSONObject options)  throws Exception {
        addViewWithOptions(view, options, middleViewIndex, topViewsVisible);
        middleViewIndex++;
    }

    public void addMiddleView(View view) {
        if (hasMiddleView) {
            removeViewAt(middleViewIndex);
        }
        setViewElevation(view, View.VISIBLE);
        addView(view, middleViewIndex, getLayoutParamsForMiddle());
        hasMiddleView = true;
    }

    public void addBottomView(View view, JSONObject options) throws Exception {
        int indexToAddBottomItem;
        if (hasMiddleView) {
            indexToAddBottomItem = middleViewIndex + 1;
        } else {
            indexToAddBottomItem = middleViewIndex;
        }
        addViewWithOptions(view, options, indexToAddBottomItem, bottomViewsVisible);
    }

    public void removeTopViews() {
        int numberTopViews = numberTopViews();

        if (numberTopViews > 0) {
            int startIndexTopView = startIndexTopView();
            setChildrenElevationsGone(startIndexTopView, numberTopViews);
            removeViews(startIndexTopView, numberTopViews);
            middleViewIndex = 0;
        }
    }

    public void removeMiddleView() {
        if (hasMiddleView) {
            setChildrenElevationsGone(middleViewIndex, 1);
            removeViewAt(middleViewIndex);
            hasMiddleView = false;
        }
    }

    public void removeBottomViews() {
        int numberBottomViews = numberBottomViews();

        if (numberBottomViews > 0) {
            int startIndexBottomView = startIndexBottomView();

            setChildrenElevationsGone(startIndexBottomView, numberBottomViews);
            removeViews(startIndexBottomView, numberBottomViews);
        }
    }

    public void setVisibilityTopViews(int visibility) {
        int numberTopViews = numberTopViews();

        if (numberTopViews > 0) {
            setChildrenVisibilities(startIndexTopView(), numberTopViews, visibility);
        }
        topViewsVisible = viewIsVisible(visibility);
    }

    public void setVisibilityBottomViews(int visibility) {
        int numberBottomViews = numberBottomViews();

        if (numberBottomViews > 0) {
            setChildrenVisibilities(startIndexBottomView(), numberBottomViews, visibility);
        }
        bottomViewsVisible = viewIsVisible(visibility);
    }

    public void setChildVisibility(View view, int visiblity) {
        if (!ViewUtilities.viewContainerContainsView(this, view)) {
            return;
        }

        if (visiblity == View.VISIBLE) {
            if (hiddenViews.contains(view)) {
                hiddenViews.remove(view);
            }
        } else {
            hiddenViews.add(view);
        }

        if (containedInTopContainer(view) && !topViewsVisible) {
            visiblity = View.GONE;
        }

        if (containedInBottomContainer(view) && !bottomViewsVisible) {
            visiblity = View.GONE;
        }

        setViewElevation(view, visiblity);
        view.setVisibility(visiblity);
    }
}
