/*
 * Copyright (C) 2014 Andrey Kulikov ([email protected])
 *
 * 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.transitionseverywhere.utils;

import android.annotation.TargetApi;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;

import com.transitionseverywhere.R;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH)
public class ViewUtils {

    static class BaseViewUtils {

        @Nullable
        private static final Field FIELD_VIEW_FLAGS =
                ReflectionUtils.getPrivateField(View.class, "mViewFlags");

        @Nullable
        private static final Field FIELD_LAYOUT_PARAMS =
                ReflectionUtils.getPrivateField(View.class, "mLayoutParams");

        private static final int VIEW_VISIBILITY_MASK = 0x0000000C;

        @Nullable
        private static final Method METHOD_SET_FRAME =
                ReflectionUtils.getPrivateMethod(View.class, "setFrame", int.class, int.class,
                        int.class, int.class);

        public float getTransitionAlpha(@NonNull View v) {
            return v.getAlpha();
        }

        public boolean isLaidOut(@NonNull View v, boolean defaultValue) {
            return defaultValue;
        }

        public void setClipBounds(@NonNull View v, @Nullable Rect clipBounds) {
            // TODO: Implement support behavior
        }

        @Nullable
        public Rect getClipBounds(@NonNull View v) {
            // TODO: Implement support behavior
            return null;
        }

        public void setTransitionName(@NonNull View v, @Nullable String name) {
            v.setTag(R.id.transitionName, name);
        }

        @Nullable
        public String getTransitionName(@NonNull View v) {
            return (String) v.getTag(R.id.transitionName);
        }

        public void setTranslationZ(@NonNull View view, float z) {
            // do nothing
        }

        public float getTranslationZ(@NonNull View view) {
            return 0;
        }

        @Nullable
        public View addGhostView(@NonNull View view, @NonNull ViewGroup viewGroup, @Nullable Matrix matrix) {
            return null;
        }

        public void removeGhostView(@NonNull View view) {
            // do nothing
        }

        public void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
            // TODO: Implement support behavior
        }

        public void transformMatrixToLocal(@NonNull View v, @NonNull Matrix matrix) {
            // TODO: Implement support behavior
        }

        public void setAnimationMatrix(@NonNull View view, @Nullable Matrix matrix) {
            // TODO: Implement support behavior
        }

        @Nullable
        public Object getWindowId(@NonNull View view) {
            return view.getWindowToken();
        }

        public boolean isRtl(@NonNull View view) {
            return false;
        }

        public void setHasTransientState(@NonNull View view, boolean hasTransientState) {
            // do nothing; API doesn't exist
        }

        public boolean hasTransientState(@NonNull View view) {
            return false;
        }

        public void setTransitionVisibility(@NonNull View v, int visibility) {
            int value = (Integer) ReflectionUtils.getFieldValue(v, 0, FIELD_VIEW_FLAGS);
            value = (value & ~VIEW_VISIBILITY_MASK) | visibility;
            ReflectionUtils.setFieldValue(v, FIELD_VIEW_FLAGS, value);
        }

        public void setLeftTopRightBottom(@NonNull View v, int left, int top, int right, int bottom) {
            ReflectionUtils.invoke(v, null, METHOD_SET_FRAME, left, top, right, bottom);
        }

        public void setLayoutParamsSilently(@NonNull View view, @Nullable ViewGroup.LayoutParams layoutParams) {
            ReflectionUtils.setFieldValue(view, FIELD_LAYOUT_PARAMS, layoutParams);
        }

    }

    @TargetApi(VERSION_CODES.JELLY_BEAN)
    static class ViewUtilsJellyBean extends BaseViewUtils {
        @Override
        public void setHasTransientState(@NonNull View view, boolean hasTransientState) {
            view.setHasTransientState(hasTransientState);
        }

        @Override
        public boolean hasTransientState(@NonNull View view) {
            return view.hasTransientState();
        }
    }

    @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
    static class ViewUtilsJellyBeanMR1 extends ViewUtilsJellyBean {
        @Override
        public boolean isRtl(@NonNull View view) {
            return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
        }
    }

    @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
    static class ViewUtilsJellyBeanMR2 extends ViewUtilsJellyBeanMR1 {
        @Override
        public void setClipBounds(@NonNull View v, @Nullable Rect clipBounds) {
            v.setClipBounds(clipBounds);
        }

        @Override
        @Nullable
        public Rect getClipBounds(@NonNull View v) {
            return v.getClipBounds();
        }

        @Override
        @Nullable
        public Object getWindowId(@NonNull View view) {
            return view.getWindowId();
        }
    }

    @TargetApi(VERSION_CODES.KITKAT)
    static class ViewUtilsKitKat extends ViewUtilsJellyBeanMR2 {
        @Override
        public boolean isLaidOut(@NonNull View v, boolean defaultValue) {
            return v.isLaidOut();
        }
    }

    @NonNull
    private static final BaseViewUtils IMPL;

    static {
        final int version = VERSION.SDK_INT;
        if (version >= VERSION_CODES.LOLLIPOP_MR1) {
            IMPL = new ViewUtilsLollipopMr1();
        } else if (version >= VERSION_CODES.LOLLIPOP) {
            IMPL = new ViewUtilsLollipop();
        } else if (version >= VERSION_CODES.KITKAT) {
            IMPL = new ViewUtilsKitKat();
        } else if (version >= VERSION_CODES.JELLY_BEAN_MR2) {
            IMPL = new ViewUtilsJellyBeanMR2();
        } else if (version >= VERSION_CODES.JELLY_BEAN_MR1) {
            IMPL = new ViewUtilsJellyBeanMR1();
        } else if (version >= VERSION_CODES.JELLY_BEAN) {
            IMPL = new ViewUtilsJellyBean();
        } else {
            IMPL = new BaseViewUtils();
        }
    }

    public static float getTransitionAlpha(@NonNull View v) {
        return IMPL.getTransitionAlpha(v);
    }

    public static boolean isLaidOut(@NonNull View v, boolean defaultValue) {
        return IMPL.isLaidOut(v, defaultValue);
    }

    public static void setClipBounds(@NonNull View v, @Nullable Rect clipBounds) {
        IMPL.setClipBounds(v, clipBounds);
    }

    @Nullable
    public static Rect getClipBounds(@NonNull View v) {
        return IMPL.getClipBounds(v);
    }

    public static void setTransitionName(@NonNull View v, @Nullable String name) {
        IMPL.setTransitionName(v, name);
    }

    @Nullable
    public static String getTransitionName(@NonNull View v) {
        return IMPL.getTransitionName(v);
    }

    public static float getTranslationZ(@NonNull View view) {
        return IMPL.getTranslationZ(view);
    }

    public static void setTranslationZ(@NonNull View view, float z) {
        IMPL.setTranslationZ(view, z);
    }

    public static void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
        IMPL.transformMatrixToGlobal(view, matrix);
    }

    public static void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
        IMPL.transformMatrixToLocal(view, matrix);
    }

    public static void setAnimationMatrix(@NonNull View view, @Nullable Matrix matrix) {
        IMPL.setAnimationMatrix(view, matrix);
    }

    @Nullable
    public static View addGhostView(@NonNull View view, @NonNull ViewGroup viewGroup, @Nullable Matrix matrix) {
        return IMPL.addGhostView(view, viewGroup, matrix);
    }

    public static void removeGhostView(@NonNull View view) {
        IMPL.removeGhostView(view);
    }

    @Nullable
    public static Object getWindowId(@NonNull View view) {
        return IMPL.getWindowId(view);
    }

    public static boolean isRtl(@NonNull View view) {
        return IMPL.isRtl(view);
    }

    public static boolean hasTransientState(@NonNull View view) {
        return IMPL.hasTransientState(view);
    }

    public static void setHasTransientState(@NonNull View view, boolean hasTransientState) {
        IMPL.setHasTransientState(view, hasTransientState);
    }

    /**
     * Change the visibility of the View without triggering any other changes. This is
     * important for transitions, where visibility changes should not adjust focus or
     * trigger a new layout. This is only used when the visibility has already been changed
     * and we need a transient value during an animation. When the animation completes,
     * the original visibility value is always restored.
     *
     * @param visibility One of View.VISIBLE, View.INVISIBLE, or View.GONE.
     */
    public static void setTransitionVisibility(@NonNull View view, int visibility) {
        IMPL.setTransitionVisibility(view, visibility);
    }

    public static void setLeftTopRightBottom(@NonNull View view, int left, int top, int right, int bottom) {
        IMPL.setLeftTopRightBottom(view, left, top, right, bottom);
    }

    public static void setLayoutParamsSilently(@NonNull View view, @Nullable ViewGroup.LayoutParams layoutParams) {
        IMPL.setLayoutParamsSilently(view, layoutParams);
    }
}