package de.sebse.fuplanner.tools.ui.cardview;


import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.view.animation.Transformation;
import android.widget.ImageButton;

import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.tools.UtilsUi;
import de.sebse.fuplanner.tools.logging.Logger;

/**
 *
 * Copyright (c) 2018 Alessandro Sperotti
 *
 * 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.
 *
 *
 * Created by alessandros on 23/02/2018.
 * @author Alessandro Sperotti
 */

public class ExpandableCardView extends CardView {
    private int innerViewRes;
    private int outerViewRes;

    private View innerView;
    private View outerView;
    private ImageButton imageButton;

    private static final int DEFAULT_ANIM_DURATION = 350;
    private long animDuration = DEFAULT_ANIM_DURATION;

    private final static int COLLAPSING = 0;
    private final static int EXPANDING = 1;

    private boolean isExpanded = false;
    private boolean isExpanding = false;
    private boolean isCollapsing = false;
    private boolean startExpanded = false;

    private int collapsedHeight = 0;
    private int expandedHeight = 0;

    private OnExpandedListener listener;

    private final OnClickListener defaultClickListener = v -> {
        if(isExpanded()) collapse();
        else expand();
    };
    private final Logger log = new Logger(this);

    public ExpandableCardView(Context context) {
        super(context);
        initView(context);
    }

    public ExpandableCardView(Context context, AttributeSet attrs) {
        super(context, attrs);

        initAttributes(context, attrs);
        initView(context);
    }

    public ExpandableCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initAttributes(context, attrs);
        initView(context);
    }

    private void initView(Context context){
        //Inflating View
        imageButton = new ImageButton(context);
        imageButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_down));
        imageButton.setPadding(
                (int) UtilsUi.convertDpToPixels(getContext(), 10),
                (int) UtilsUi.convertDpToPixels(getContext(), 10),
                (int) UtilsUi.convertDpToPixels(getContext(), 10),
                (int) UtilsUi.convertDpToPixels(getContext(), 10)
        );
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
            imageButton.setBackgroundResource(outValue.resourceId);
        }
        isExpanded = startExpanded;
    }

    private void initAttributes(Context context, AttributeSet attrs){
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableCardView);

        innerViewRes = typedArray.getResourceId(R.styleable.ExpandableCardView_inner_view, View.NO_ID);
        outerViewRes = typedArray.getResourceId(R.styleable.ExpandableCardView_outer_view, View.NO_ID);
        animDuration = typedArray.getInteger(R.styleable.ExpandableCardView_animationDuration, DEFAULT_ANIM_DURATION);
        startExpanded = typedArray.getBoolean(R.styleable.ExpandableCardView_startExpanded, false);
        typedArray.recycle();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        innerView = inflateChild(innerViewRes);
        outerView = inflateChild(outerViewRes);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setElevation(UtilsUi.convertDpToPixels(getContext(), 4));
        }

        setOnClickListener(defaultClickListener);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        removeAllViews();
        int x = getPaddingLeft();
        int y = getPaddingTop();
        addView(outerView);
        outerView.layout(x, y, x+outerView.getMeasuredWidth(), y+outerView.getMeasuredHeight());
        addView(imageButton);
        imageButton.layout(
                getMeasuredWidth() - getPaddingRight() - imageButton.getMeasuredWidth(),
                y,
                getMeasuredWidth() - getPaddingRight(),
                y+imageButton.getMeasuredHeight()
        );
        addView(innerView);
        innerView.layout(x, y+outerView.getMeasuredHeight(), x+innerView.getMeasuredWidth(), y+outerView.getMeasuredHeight()+innerView.getMeasuredHeight());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (MeasureSpec.getSize(widthMeasureSpec) == 0 && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED || MeasureSpec.getSize(heightMeasureSpec) == 0 && MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED) {
            log.w("This should not happen! Invalid dimension size");
            setMeasuredDimension(reconcileSize(10, widthMeasureSpec), reconcileSize(10, heightMeasureSpec));
            return;
        }

        int desiredWidth;
        int desiredHeight;

        int widthMeasure = atMostSpec(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec);
        int heightMeasure = atMostSpec(MeasureSpec.getSize(heightMeasureSpec), heightMeasureSpec);
        imageButton.measure(widthMeasure, heightMeasure);

        widthMeasure = atMostExactlySpec(Math.max(0, MeasureSpec.getSize(widthMeasureSpec)-imageButton.getMeasuredWidth()), widthMeasureSpec);
        heightMeasure = atMostSpec(MeasureSpec.getSize(heightMeasureSpec), heightMeasureSpec);
        outerView.measure(widthMeasure, heightMeasure);
        desiredWidth = imageButton.getMeasuredWidth() + outerView.getMeasuredWidth();
        desiredHeight = Math.max(imageButton.getMeasuredHeight(), outerView.getMeasuredHeight());

        widthMeasure = atMostSpec(MeasureSpec.getSize(widthMeasureSpec), widthMeasureSpec);
        heightMeasure = atMostSpec(Math.max(0, MeasureSpec.getSize(heightMeasureSpec)-desiredHeight), heightMeasureSpec);
        innerView.measure(widthMeasure, heightMeasure);
        desiredWidth = Math.max(desiredWidth, innerView.getMeasuredWidth());
        desiredHeight += innerView.getMeasuredHeight();

        desiredWidth += getPaddingLeft() + getPaddingRight();
        desiredHeight += getPaddingTop() + getPaddingBottom();

        if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
            expandedHeight = desiredHeight;
            collapsedHeight = desiredHeight - innerView.getMeasuredHeight();
        }

        setMeasuredDimension(reconcileSize(desiredWidth, widthMeasureSpec), reconcileSize(isExpanded ? expandedHeight : collapsedHeight, heightMeasureSpec));
    }

    private void expand() {
        final int initialHeight = this.getHeight();
        int targetHeight = expandedHeight;

        if(targetHeight - initialHeight != 0) {
            animateViews(initialHeight, targetHeight - initialHeight, EXPANDING);
        }
    }

    private void collapse() {
        final int initialHeight = this.getHeight();
        int targetHeight = collapsedHeight;

        if(initialHeight - targetHeight != 0) {
            animateViews(initialHeight, initialHeight - targetHeight, COLLAPSING);
        }

    }

    private boolean isExpanded() {
        return isExpanded;
    }

    private void animateViews(final int initialHeight, final int distance, final int animationType){

        Animation expandAnimation = new Animation() {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                if (interpolatedTime == 1) {
                    //Setting isExpanding/isCollapsing to false
                    isExpanding = false;
                    isCollapsing = false;

                    if (listener != null) {
                        if (animationType == EXPANDING)
                            listener.onExpandChanged(ExpandableCardView.this, true);
                        else
                            listener.onExpandChanged(ExpandableCardView.this, false);
                    }
                }

                ExpandableCardView.this.getLayoutParams().height = animationType == EXPANDING
                        ? (int) (initialHeight + (distance * interpolatedTime))
                        : (int) (initialHeight - (distance * interpolatedTime));
                ExpandableCardView.this.requestLayout();

            }

            @Override
            public boolean willChangeBounds() {
                return true;
            }
        };

        RotateAnimation arrowAnimation = animationType == EXPANDING ?
                new RotateAnimation(0,180,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                        0.5f) :
                new RotateAnimation(180,0,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                        0.5f);

        arrowAnimation.setFillAfter(true);


        arrowAnimation.setDuration(animDuration);
        expandAnimation.setDuration(animDuration);

        isExpanding = animationType == EXPANDING;
        isCollapsing = animationType == COLLAPSING;

        startAnimation(expandAnimation);
        imageButton.startAnimation(arrowAnimation);
        isExpanded = animationType == EXPANDING;

    }

    private boolean isExpanding(){
        return isExpanding;
    }

    private boolean isCollapsing(){
        return isCollapsing;
    }

    private boolean isMoving(){
        return isExpanding() || isCollapsing();
    }

    public void setOnExpandedListener(OnExpandedListener listener) {
        this.listener = listener;
    }

    public void removeOnExpandedListener(){
        this.listener = null;
    }

    private View inflateChild(@LayoutRes int resId) {
        return inflate(getContext(), resId, null);
    }

    public View getOuterView() {
        return outerView;
    }

    public View getInnerView() {
        return innerView;
    }

    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        if(imageButton != null) imageButton.setOnClickListener(l);
        super.setOnClickListener(l);
    }

    private long getAnimDuration() {
        return animDuration;
    }

    private void setAnimDuration(long animDuration) {
        this.animDuration = animDuration;
    }

    private int reconcileSize(int contentSize, int measureSpec) {
        final int mode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        switch(mode) {
            case MeasureSpec.EXACTLY:
                return specSize;
            case MeasureSpec.AT_MOST:
                if (contentSize < specSize) {
                    return contentSize;
                } else {
                    log.w("Your content may be cropped!");
                    return specSize;
                }
            case MeasureSpec.UNSPECIFIED:
            default:
                return contentSize;
        }
    }

    private int atMostSpec(int atMostSpace, int measureSpec) {
        if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED)
            return measureSpec;
        else
            return MeasureSpec.makeMeasureSpec(atMostSpace, MeasureSpec.AT_MOST);
    }

    private int atMostExactlySpec(int atMostExactlySpace, int measureSpec) {
        if (MeasureSpec.getMode(measureSpec) == MeasureSpec.UNSPECIFIED)
            return measureSpec;
        else
            return MeasureSpec.makeMeasureSpec(atMostExactlySpace, MeasureSpec.getMode(measureSpec));
    }

    public void reset() {
        long anim = getAnimDuration();
        this.setAnimDuration(0);
        if (startExpanded) {
            this.expand();
        } else {
            this.collapse();
        }
        this.setAnimDuration(anim);
    }


    /**
     * Interfaces
     */

    interface OnExpandedListener {

        void onExpandChanged(View v, boolean isExpanded);

    }

}


